Data Set
The data set consists of 7,043 observations of 21 variables as shown below:
- customerID: Unique identifier for each customer
- gender: Gender of customer
- SeniorCitizen: A binary variable which identifies if a customer is a Senior Citizen (1) or not (0)
- Partner: A binary variable which identifies if a customer has a partner (Yes/No)
- Dependents: A binary variable which identifies if a customer has any dependents (Yes/No)
- tenure: The tenure of association of the customer with the company in months
- PhoneService: A binary variable which identifies if a customer has phone service
- MultipleLines: Variable that identfies if customers having phone lines have multiple lines or not
- InternetService: This variable identifies the type of internet service that the customer has
- OnlineSecurity: Variable that identfies if customers having internet service have online security or not
- Onlinebackup: Variable that identfies if customers having internet service have online backup or not
- DeviceProtection: Variable that identfies if customers having internet service have device protection or not
- TechSupport: Variable that identfies if customers having internet service have availed tech support or not
- StreamingTV: Variable that identfies if customers having internet service have TV Streaming or not
- StreamingMovies: Variable that identfies if customers having internet service have Movie Streaming or not
- Contract: Variable that identfies the contract type of the customers
- PaperlessBilling: Variable that identfies if the customer has opted for paperless billing
- PaymentMethod: Variable that identfies the payment method of the customer
- MonthlyCharges: Variable that identfies the monthly bill amount of the customer
- TotalCharges: Variable that identfies the overall bill amount of the customer throughout the tenure
- Churn: Variable that identfies if the customer has cancelled the company’s services
Let’s look at the distinct values present in each categorical variable.
Some factor variables have 3 types, let’s see what those values are, and if we can group them in anyway.
options(width = 10)
telco_customer_churn_raw_data %>%
summarise_all(funs(n_distinct(.)))
as.data.frame(table(telco_customer_churn_raw_data$MultipleLines))
as.data.frame(table(telco_customer_churn_raw_data$InternetService))
as.data.frame(table(telco_customer_churn_raw_data$OnlineSecurity))
as.data.frame(table(telco_customer_churn_raw_data$OnlineBackup))
as.data.frame(table(telco_customer_churn_raw_data$DeviceProtection))
as.data.frame(table(telco_customer_churn_raw_data$TechSupport))
as.data.frame(table(telco_customer_churn_raw_data$StreamingMovies))
as.data.frame(table(telco_customer_churn_raw_data$StreamingTV))
As we can see, some of the factors have 3 values, but the categories “No Internet Service”/“No Phone Service” are not adding any additional value as that information is already captured in a separate variable. So, let’s update these two values to “No”.
Also, since all categorical variables are in yes/no format except Senior Citizen, we can transform that also into Yes/No format. Let’s also transform the output variable Churn into 1/0 format so that we do not have to dummify it further.
#Transform MultipleLines Variable
telco_customer_churn_transformed_data <- telco_customer_churn_raw_data %>%
mutate(MultipleLines = ifelse(MultipleLines=="No phone service","No",MultipleLines))
#Transform OnlineSecurity Variable
telco_customer_churn_transformed_data <- telco_customer_churn_transformed_data %>%
mutate(OnlineSecurity = ifelse(OnlineSecurity=="No internet service","No",OnlineSecurity))
#Transform OnlilneBackup Variable
telco_customer_churn_transformed_data <- telco_customer_churn_transformed_data %>%
mutate(OnlineBackup = ifelse(OnlineBackup=="No internet service","No",OnlineBackup))
#Transform DeviceProtection Variable
telco_customer_churn_transformed_data <- telco_customer_churn_transformed_data %>%
mutate(DeviceProtection = ifelse(DeviceProtection=="No internet service","No",DeviceProtection))
#Transform TechSupport Variable
telco_customer_churn_transformed_data <- telco_customer_churn_transformed_data %>%
mutate(TechSupport = ifelse(TechSupport=="No internet service","No",TechSupport))
#Transform Streaming Movies Variable
telco_customer_churn_transformed_data <- telco_customer_churn_transformed_data %>%
mutate(StreamingMovies = ifelse(StreamingMovies=="No internet service","No",StreamingMovies))
#Transform Streaming TV Variable
telco_customer_churn_transformed_data <- telco_customer_churn_transformed_data %>%
mutate(StreamingTV = ifelse(StreamingTV=="No internet service","No",StreamingTV))
#Transform Senior Citizen Variable
telco_customer_churn_transformed_data <- telco_customer_churn_transformed_data %>%
mutate(SeniorCitizen = ifelse(SeniorCitizen==1,"Yes","No"))
#Transform Churn Variable
telco_customer_churn_transformed_data <- telco_customer_churn_transformed_data %>%
mutate(Churn = ifelse(Churn=="Yes",1,0))
Let’s look at the structure of the dataset.
telco_customer_churn_transformed_data %>%
plot_str()
From the above graph we can notice that some categorical variables are currently ‘characters’. We can convert them to factors for more accurate representation.
telco_customer_churn_transformed_data <- telco_customer_churn_transformed_data %>%
mutate(SeniorCitizen = as.factor(SeniorCitizen),
gender = as.factor(gender),
Partner = as.factor(Partner),
Dependents = as.factor(Dependents),
SeniorCitizen = as.factor(SeniorCitizen),
PhoneService = as.factor(PhoneService),
MultipleLines = as.factor(MultipleLines),
InternetService = as.factor(InternetService),
OnlineSecurity = as.factor(OnlineSecurity),
OnlineBackup = as.factor(OnlineBackup),
DeviceProtection = as.factor(DeviceProtection),
TechSupport = as.factor(TechSupport),
StreamingTV = as.factor(StreamingTV),
StreamingMovies = as.factor(StreamingMovies),
PaperlessBilling = as.factor(PaperlessBilling),
Contract = as.factor(Contract),
PaymentMethod = as.factor(PaymentMethod),
Churn = as.factor(Churn))
Data Exploration
As the graph below shows, 99.8% of the observations are complete.
telco_customer_churn_transformed_data %>%
plot_intro()

The below graph shows us that “TotalCharges” has missing values. Though they are very few, we will check the reason behind this in further sections.

From the below count we can see that there are no duplicate Customer IDs in the dataset. There are 7043 records and 7043 unique Customer IDs
telco_customer_churn_transformed_data %>%
summarise(Total_Num_Records = n(),Unique_CustomerIDs = n_distinct(customerID))
From the below frequency distributions we can see that there are no duplicate values in any categorical variable. The data appears to be clean.
#Customer ID related information has already been check above. It is not required here.
telco_customer_churn_transformed_data %>%
select(-customerID) %>%
plot_bar(ggtheme = theme_light(), title = "Categorical Variable Distributions")


From the below histograms we can see that there are some customers with a tenure of 0 months. We assume that these are new customers, added in the past month.
Also, notice that Total Charges is skewed to the right, which makes sense as we have customers with high tenure. The total charges for them will have accumulated for a long period.
telco_customer_churn_transformed_data %>%
plot_histogram(ggtheme = theme_light())

The missing values of “TotalCharges” that we noticed in earlier sections is because of the new customers (tenure=0). Since they have not completed a billing cycle yet, their Total Charge value is missing. We can confirm this with the result below, which checks if there are any missing TotalCharges values for observations with non-zero tenures. As we can see there are none. So, we can conclude that missing TotalCharges are because of 0 tenure.
telco_customer_churn_transformed_data %>%
filter(is.na(TotalCharges) && tenure!=0)
Let’s convert missing TotalCharges to 0 and plot missing values again.
#Check if TotalCharges is NA and replace with 0
telco_customer_churn_cleaned_data <- telco_customer_churn_transformed_data %>%
mutate(TotalCharges = ifelse(is.na(TotalCharges),0,TotalCharges))
telco_customer_churn_cleaned_data %>%
plot_missing()

Before proceeding to modeling, let’s check if there are any trends in the data.
The overall categorical variables in this data set can be classified as follows:
- variables that define attributes of a customer
- variables that define attributes of the service
Let’s check how these variables affect Churn by plotting them
Customer Atrributes
From the graphs below we can notice that:
- Gender does not have a clear trend for Churn; the gender split of churned customers is nearly 50-50.
- Senior Citizens and customers with dependents seems to have lower churn, we can check the effect of these variable on churn further.
#using plotly here just to explore the functinalities in this package more
#Plot by SeniorCitizen
telco_customer_churn_cleaned_data %>%
group_by(Churn,SeniorCitizen) %>%
summarise(count = n()) %>%
plot_ly(x= ~SeniorCitizen, y= ~count, name= ~Churn, type = 'bar', width = 400, height = 400) %>%
layout(title = 'Senior Citizen distribution based on Churn')
#Plot by Gender
telco_customer_churn_cleaned_data %>%
group_by(Churn,gender) %>%
summarise(count = n()) %>%
plot_ly(x= ~gender, y= ~count, name= ~Churn, type = 'bar', width = 400, height = 400) %>%
layout(title = 'Gender distribution based on Churn')
#Plot by Dependents
telco_customer_churn_cleaned_data %>%
group_by(Churn,Dependents) %>%
summarise(count = n()) %>%
plot_ly(x= ~Dependents, y= ~count, name= ~Churn, type = 'bar', width = 400, height = 400) %>%
layout(title = 'Dependent distribution based on Churn')
#Plot by Partner
telco_customer_churn_cleaned_data %>%
group_by(Churn,Partner) %>%
summarise(count = n()) %>%
plot_ly(x= ~Partner, y= ~count, name= ~Churn, type = 'bar', width = 400, height = 400) %>%
layout(title = 'Partner distribution based on Churn')
Service Atrributes
From the plots below we can infer the following:
- Customers with Fiber Optic Internet Sevice seems to churn more
- Customer with Month-to-month contracts seems to churn more
- Customers who pay their bills through Electronic Check seem to churn more
- Customers who have opted for paperless bills seem to churn more
#using ggplot here just to explore the functinalities in this package more
#Plot by SeniorCitizen
p1 <- telco_customer_churn_cleaned_data %>%
group_by(Churn,PhoneService) %>%
summarise(count = n()) %>%
ggplot(aes(PhoneService,count,fill = Churn))+
geom_bar(stat = "identity", position = "dodge")+
coord_flip()+
ggtitle("PhoneService distribution") +
labs(
x = "Phone Service",
y = "Frequency"
) +
theme(
plot.title = element_text(hjust = 0.5, face = "bold"),
plot.background = element_rect(
fill = "white",
colour = "lightgrey",
size = 0.75),
axis.title.x = element_text(face = "bold"),
axis.title.y = element_text(face = "bold"),
panel.background = element_rect(fill = "white"),
panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
)
#Plot by Gender
p2 <- telco_customer_churn_cleaned_data %>%
group_by(Churn,MultipleLines) %>%
summarise(count = n()) %>%
ggplot(aes(MultipleLines,count,fill = Churn))+
geom_bar(stat = "identity", position = "dodge")+
coord_flip()+
ggtitle("MultipleLines distribution") +
labs(
x = "Multiple Lines",
y = "Frequency"
) +
theme(
plot.title = element_text(hjust = 0.5, face = "bold"),
plot.background = element_rect(
fill = "white",
colour = "lightgrey",
size = 0.75),
axis.title.x = element_text(face = "bold"),
axis.title.y = element_text(face = "bold"),
panel.background = element_rect(fill = "white"),
panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
)
#Plot by Dependents
p3 <- telco_customer_churn_cleaned_data %>%
group_by(Churn,InternetService) %>%
summarise(count = n()) %>%
ggplot(aes(InternetService,count,fill = Churn))+
geom_bar(stat = "identity", position = "dodge")+
coord_flip()+
ggtitle("InternetService distribution") +
labs(
x = "Internet Service",
y = "Frequency"
) +
theme(
plot.title = element_text(hjust = 0.5, face = "bold"),
plot.background = element_rect(
fill = "white",
colour = "lightgrey",
size = 0.75),
axis.title.x = element_text(face = "bold"),
axis.title.y = element_text(face = "bold"),
panel.background = element_rect(fill = "white"),
panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
)
#Plot by OnlineSecurity
p4 <- telco_customer_churn_cleaned_data %>%
group_by(Churn,OnlineSecurity) %>%
summarise(count = n()) %>%
ggplot(aes(OnlineSecurity,count,fill = Churn))+
geom_bar(stat = "identity", position = "dodge")+
coord_flip()+
ggtitle("OnlineSecurity distribution") +
labs(
x = "Online Security",
y = "Frequency"
) +
theme(
plot.title = element_text(hjust = 0.5, face = "bold"),
plot.background = element_rect(
fill = "white",
colour = "lightgrey",
size = 0.75),
axis.title.x = element_text(face = "bold"),
axis.title.y = element_text(face = "bold"),
panel.background = element_rect(fill = "white"),
panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
)
#Plot by OnlineBackup
p5 <- telco_customer_churn_cleaned_data %>%
group_by(Churn,OnlineBackup) %>%
summarise(count = n()) %>%
ggplot(aes(OnlineBackup,count,fill = Churn))+
geom_bar(stat = "identity", position = "dodge")+
coord_flip()+
ggtitle("OnlineBackup distribution") +
labs(
x = "Online Backup",
y = "Frequency"
) +
theme(
plot.title = element_text(hjust = 0.5, face = "bold"),
plot.background = element_rect(
fill = "white",
colour = "lightgrey",
size = 0.75),
axis.title.x = element_text(face = "bold"),
axis.title.y = element_text(face = "bold"),
panel.background = element_rect(fill = "white"),
panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
)
#Plot by DeviceProtection
p6 <- telco_customer_churn_cleaned_data %>%
group_by(Churn,DeviceProtection) %>%
summarise(count = n()) %>%
ggplot(aes(DeviceProtection,count,fill = Churn))+
geom_bar(stat = "identity", position = "dodge")+
coord_flip()+
ggtitle("DeviceProtection distribution") +
labs(
x = "Device Protection",
y = "Frequency"
) +
theme(
plot.title = element_text(hjust = 0.5, face = "bold"),
plot.background = element_rect(
fill = "white",
colour = "lightgrey",
size = 0.75),
axis.title.x = element_text(face = "bold"),
axis.title.y = element_text(face = "bold"),
panel.background = element_rect(fill = "white"),
panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
)
#Plot by TechSupport
p7 <- telco_customer_churn_cleaned_data %>%
group_by(Churn,TechSupport) %>%
summarise(count = n()) %>%
ggplot(aes(TechSupport,count,fill = Churn))+
geom_bar(stat = "identity", position = "dodge")+
coord_flip()+
ggtitle("TechSupport distribution") +
labs(
x = "Tech Support",
y = "Frequency"
) +
theme(
plot.title = element_text(hjust = 0.5, face = "bold"),
plot.background = element_rect(
fill = "white",
colour = "lightgrey",
size = 0.75),
axis.title.x = element_text(face = "bold"),
axis.title.y = element_text(face = "bold"),
panel.background = element_rect(fill = "white"),
panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
)
#Plot by StreamingTV
p8 <- telco_customer_churn_cleaned_data %>%
group_by(Churn,StreamingTV) %>%
summarise(count = n()) %>%
ggplot(aes(StreamingTV,count,fill = Churn))+
geom_bar(stat = "identity", position = "dodge")+
coord_flip()+
ggtitle("StreamingTV distribution") +
labs(
x = "StreamingTV",
y = "Frequency"
) +
theme(
plot.title = element_text(hjust = 0.5, face = "bold"),
plot.background = element_rect(
fill = "white",
colour = "lightgrey",
size = 0.75),
axis.title.x = element_text(face = "bold"),
axis.title.y = element_text(face = "bold"),
panel.background = element_rect(fill = "white"),
panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
)
#Plot by StreamingMovies
p9 <- telco_customer_churn_cleaned_data %>%
group_by(Churn,StreamingMovies) %>%
summarise(count = n()) %>%
ggplot(aes(StreamingMovies,count,fill = Churn))+
geom_bar(stat = "identity", position = "dodge")+
coord_flip()+
ggtitle("StreamingMovies distribution") +
labs(
x = "Streaming Movies",
y = "Frequency"
) +
theme(
plot.title = element_text(hjust = 0.5, face = "bold"),
plot.background = element_rect(
fill = "white",
colour = "lightgrey",
size = 0.75),
axis.title.x = element_text(face = "bold"),
axis.title.y = element_text(face = "bold"),
panel.background = element_rect(fill = "white"),
panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
)
#Plot by PaperlessBilling
p10 <- telco_customer_churn_cleaned_data %>%
group_by(Churn,PaperlessBilling) %>%
summarise(count = n()) %>%
ggplot(aes(PaperlessBilling,count,fill = Churn))+
geom_bar(stat = "identity", position = "dodge")+
coord_flip()+
ggtitle("PaperlessBilling distribution") +
labs(
x = "Paperless Billing",
y = "Frequency"
) +
theme(
plot.title = element_text(hjust = 0.5, face = "bold"),
plot.background = element_rect(
fill = "white",
colour = "lightgrey",
size = 0.75),
axis.title.x = element_text(face = "bold"),
axis.title.y = element_text(face = "bold"),
panel.background = element_rect(fill = "white"),
panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
)
#Plot by Contract
p11 <- telco_customer_churn_cleaned_data %>%
group_by(Churn,Contract) %>%
summarise(count = n()) %>%
ggplot(aes(Contract,count,fill = Churn))+
geom_bar(stat = "identity", position = "dodge")+
coord_flip()+
ggtitle("Contract Type distribution") +
labs(
x = "Contract Type",
y = "Frequency"
) +
theme(
plot.title = element_text(hjust = 0.5, face = "bold"),
plot.background = element_rect(
fill = "white",
colour = "lightgrey",
size = 0.75),
axis.title.x = element_text(face = "bold"),
axis.title.y = element_text(face = "bold"),
panel.background = element_rect(fill = "white"),
panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
)
#Plot by PaymentMethod
p12 <- telco_customer_churn_cleaned_data %>%
group_by(Churn,PaymentMethod) %>%
summarise(count = n()) %>%
ggplot(aes(PaymentMethod,count,fill = Churn))+
geom_bar(stat = "identity", position = "dodge")+
coord_flip()+
ggtitle("PaymentMethod distribution") +
labs(
x = "Payment Method",
y = "Frequency"
) +
theme(
plot.title = element_text(hjust = 0.5, face = "bold"),
plot.background = element_rect(
fill = "white",
colour = "lightgrey",
size = 0.75),
axis.title.x = element_text(face = "bold"),
axis.title.y = element_text(face = "bold"),
panel.background = element_rect(fill = "white"),
panel.grid.major.x = element_line(colour = "#C4DFE6", size = 0.05)
)
grid.arrange(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12)

Other Attributes
Let’s look at the following variables in this section:
- Tenure
- Monthly Charges
- Overall Charges
We can notice that customers who churned were predominantly from lower tenure groups. So, this could be an important variable for our modeling. Overallcharges also has the same distribution with respect to churn, but that could be because it has strong correlation with tenure (OverallCharges = Tenure x MonthlyCharges).
#Tenure
telco_customer_churn_cleaned_data %>%
plot_ly(x= ~tenure, name = ~Churn, type = 'histogram', showlegend = T, width = 500, height = 400) %>%
layout(title = 'Tenure distribution by Churn')
#Monthly Charges
telco_customer_churn_cleaned_data %>%
plot_ly(x= ~MonthlyCharges, name = ~Churn, type = 'histogram', showlegend = T, width = 500, height = 400) %>%
layout(title = 'Monthly Charges distribution by Churn')
#Overall Charges
telco_customer_churn_cleaned_data %>%
plot_ly(x= ~TotalCharges, name = ~Churn, type = 'histogram', showlegend = T, width = 500, height = 400) %>%
layout(title = 'Total Charges distribution by Churn')
By plotting Tenure against MonthlyCharges, we can notice in the below graph that churned customers are mostly concentrated in the low tenure + high monthly charges region. These two variables could be useful in our model.
telco_customer_churn_cleaned_data %>%
plot_ly(x = ~tenure, y = ~MonthlyCharges, name = ~Churn, width = 1000, height = 500) %>%
layout(title = 'Monthly Charges vs Tenure by Churn')
By plotting Tenure against TotalCharges, we can notice in the below graph that churned customers are mostly concentrated in the low tenure + low TotalCharges region.
telco_customer_churn_cleaned_data %>%
plot_ly(x = ~tenure, y = ~TotalCharges, name = ~Churn, width = 1000, height = 500) %>%
layout(title = 'Total Charges vs Tenure by Churn')
Plotting Monthly Charges against TotalCharges may not be very useful, but let’s see if the plot can give us some insight. As we can see churned customers are in the low-side of TotalCharges (these could be customers with low tenures).
telco_customer_churn_cleaned_data %>%
plot_ly(x = ~MonthlyCharges, y = ~TotalCharges, name = ~Churn, width = 1000, height = 500) %>%
layout(title = 'Monthly Charges vs Total Charges by Churn')
Let’s plot the distribution of categorical variables specifically in churned customers to check if any variable stands out.
Customer Attributes distribution on churned customers
We saw earlier that Senior Citizen and Dependent variables could have correlation with Churn, let’s see what is the distribution of these variables in churned customers. As we can see, about 25% of the churned customers are senior citizens and 17.4 percent are those with dependents. So, these attributes may not be as useful as we thought they are afterall.
telco_customer_churn_cleaned_data %>%
filter(Churn==1) %>%
plot_ly(labels = ~SeniorCitizen, values = ~Churn, type = "pie", width = 500, height = 400) %>%
layout(title = 'SeniorCitizens distribution in Churned Customers')
telco_customer_churn_cleaned_data %>%
filter(Churn==1) %>%
plot_ly(labels = ~Dependents, values = ~Churn, type = "pie", width = 600, height = 400) %>%
layout(title = 'Dependent distribution in churned customers')
Service Attributes distribution on churned customers
We saw earlier that Payment Method, Contract Type, Paperless Billing and Internet Service Type attributes could have correlation with Churn, let’s see what is the distribution of these variables in churned customers.
As we can notice from the graphs below, all the 4 variables have a specific category that contributes to more than 50% of churned customers. These variables could be very useful for our models.
telco_customer_churn_cleaned_data %>%
filter(Churn==1) %>%
plot_ly(labels = ~PaymentMethod, values = ~Churn, type = "pie", width = 600, height = 400) %>%
layout(title = 'Payment Method distribution in Churned Customers')
telco_customer_churn_cleaned_data %>%
filter(Churn==1) %>%
plot_ly(labels = ~Contract, values = ~Churn, type = "pie", width = 600, height = 400) %>%
layout(title = 'ContractType distribution in churned customers')
telco_customer_churn_cleaned_data %>%
filter(Churn==1) %>%
plot_ly(labels = ~PaperlessBilling, values = ~Churn, type = "pie", width = 600, height = 400) %>%
layout(title = 'PaperlessBilling distribution in churned customers')
telco_customer_churn_cleaned_data %>%
filter(Churn==1) %>%
plot_ly(labels = ~InternetService, values = ~Churn, type = "pie", width = 600, height = 400) %>%
layout(title = 'InternetService distribution in churned customers')
Modeling
Dummify the data so that categorical variables are converted to multiple columns with 0/1s.
telco_customer_churn_dummified <- telco_customer_churn_cleaned_data %>%
dummify() %>%
mutate(Churn_yes = as.factor(ifelse(Churn_1==1, "Yes", "No")))
Partition data into Training and Validation datasets with a 70-30 split of the full dataset.
set.seed(10)
inTrainingData <- createDataPartition(telco_customer_churn_dummified$Churn_yes, p=0.70, list=FALSE)
training.set <- telco_customer_churn_dummified[inTrainingData,]
Totalvalidation.set <- telco_customer_churn_dummified[-inTrainingData,]
Random Forest
Let’s try some mtry values and see which value gives us a lower OOB error percentage.
After repeated trials, mtry 5 was found to give the lowest OOB error of around 21% as shown below.
As we can see from the Variable Importance Plots below, the most important variables for predicting Churn are
- tenure
- TotalCharges
- MonthlyCharges
- Contract_Month.to.month
- InternetServuce_Fiber.optic
- PaymentMethod.Electronic.check
which is pretty much consistent with what we found in our EDA.
training.set <- training.set %>%
select(-customerID, -Churn_0, -Churn_1)
#rf_fit_check <- randomForest(Churn_yes ~., data = training.set, importance = TRUE, metric = "ROC", ntree = 1001, mtry = 2)
#rf_fit_check <- randomForest(Churn_yes ~., data = training.set, importance = TRUE, metric = "ROC", ntree = 1001, mtry = 3)
#rf_fit_check <- randomForest(Churn_yes ~., data = training.set, importance = TRUE, metric = "ROC", ntree = 1001, mtry = 4)
rf_fit_check <- randomForest(Churn_yes ~., data = training.set, importance = TRUE, metric = "roc", ntree = 1001, mtry = 5)
VariableImp
Call:
randomForest(formula = Churn_yes ~ ., data = training.set, importance = TRUE, metric = "ROC", ntree = 1001, mtry = 5)
Type of random forest: classification
Number of trees: 1001
No. of variables tried at each split: 5
OOB estimate of error rate: 20.79%
Confusion matrix:
No Yes class.error
No 3242 380 0.1049144
Yes 645 664 0.4927426
varImpPlot(VariableImp)

Let’s use these variable and build a Random Forest model.
rf_fit <- train(Churn_yes ~ tenure+
MonthlyCharges+
TotalCharges+
Contract_Month.to.month+
InternetService_Fiber.optic,
data = training.set,
method = "rf",
metric = "Roc",
tunegrid=tunegrid,
trControl=control,
preProcess = c("center", "scale"),
ntree=1001)
The metric "Roc" was not in the result set. ROC will be used instead.
Let’s look at the model stistics of the Random Forest model.
As we can see the best ROC was achieved at mtry = 2. The model has high number of false negatives (number of churned customers who were classified as Not Churned). Let’s see if Logistic Regression can give us better predictions in the next section.
print(rf_fit)
Random Forest
4931 samples
5 predictor
2 classes: 'No', 'Yes'
Pre-processing: centered (5), scaled (5)
Resampling: Cross-Validated (10 fold, repeated 3 times)
Summary of sample sizes: 4438, 4437, 4438, 4437, 4439, 4438, ...
Resampling results across tuning parameters:
mtry ROC Sens Spec
2 0.8205527 0.9008828 0.4702877
3 0.8012674 0.8728120 0.4937385
5 0.7946440 0.8726291 0.4825328
ROC was used to select the optimal model using the largest value.
The final value used for the model was mtry = 2.
plot(rf_fit)

pred_test <- predict(rf_fit,newdata = Totalvalidation.set)
confusionMatrix(data=pred_test, Totalvalidation.set$Churn_yes)
Confusion Matrix and Statistics
Reference
Prediction No Yes
No 1417 305
Yes 135 255
Accuracy : 0.7917
95% CI : (0.7737, 0.8088)
No Information Rate : 0.7348
P-Value [Acc > NIR] : 7.793e-10
Kappa : 0.408
Mcnemar's Test P-Value : 7.834e-16
Sensitivity : 0.9130
Specificity : 0.4554
Pos Pred Value : 0.8229
Neg Pred Value : 0.6538
Prevalence : 0.7348
Detection Rate : 0.6709
Detection Prevalence : 0.8153
Balanced Accuracy : 0.6842
'Positive' Class : No
results_1 <- confusionMatrix(data=pred_test, Totalvalidation.set$Churn_yes)
confusion_mat1 <- as.data.frame(results_1$table)
ggplot(data = confusion_mat1, mapping = aes(x = Reference, y = Prediction)) +
ggtitle("Confusion Matrix for Random Forest") +
geom_tile(aes(fill = Freq), colour = "white") +
geom_text(aes(label = sprintf("%1.0f", Freq)), vjust = 1) +
scale_fill_gradient(low = "#B2DBD5", high = "#2B616D") +
theme_bw() + theme(legend.position = "none", plot.title = element_text(hjust = 0.5, face = "bold"))

Logistic Regression
Let’s try to find out which predictors are statistically significant for a GLM model.
As we can see, the following variables are statistically significant in this model:
- tenure
- totalCharges
- Contract_Month.to.month
- PaperlessBilling_No
- PaymentMethod_Electronic.check
as they have low P-values. This is almost similar to what we found in Random Forest, except that MonthlyCharges has not been found statistically significant here.
summary(glm_fit_check)
Call:
glm(formula = Churn_yes ~ ., family = "binomial", data = training.set)
Deviance Residuals:
Min 1Q Median 3Q Max
-1.9221 -0.6631 -0.2966 0.7216 3.3557
Coefficients: (16 not defined because of singularities)
Estimate Std. Error z value Pr(>|z|)
(Intercept) 1.462e+00 2.480e+00 0.589 0.555573
tenure -5.652e-02 7.249e-03 -7.797 6.32e-15 ***
MonthlyCharges -6.236e-02 3.798e-02 -1.642 0.100580
TotalCharges 3.005e-04 8.235e-05 3.649 0.000263 ***
gender_Female 4.064e-03 7.763e-02 0.052 0.958253
gender_Male NA NA NA NA
SeniorCitizen_No -2.301e-01 1.017e-01 -2.262 0.023702 *
SeniorCitizen_Yes NA NA NA NA
Partner_No 2.341e-02 9.347e-02 0.250 0.802275
Partner_Yes NA NA NA NA
Dependents_No 5.765e-02 1.067e-01 0.540 0.588970
Dependents_Yes NA NA NA NA
PhoneService_No -6.026e-01 7.765e-01 -0.776 0.437748
PhoneService_Yes NA NA NA NA
MultipleLines_No -5.422e-01 2.130e-01 -2.546 0.010894 *
MultipleLines_Yes NA NA NA NA
InternetService_DSL 2.276e+00 9.662e-01 2.355 0.018514 *
InternetService_Fiber.optic 4.616e+00 1.907e+00 2.421 0.015486 *
InternetService_No NA NA NA NA
OnlineSecurity_No 9.031e-02 2.135e-01 0.423 0.672335
OnlineSecurity_Yes NA NA NA NA
OnlineBackup_No -1.312e-01 2.098e-01 -0.625 0.531761
OnlineBackup_Yes NA NA NA NA
DeviceProtection_No -2.724e-01 2.099e-01 -1.298 0.194367
DeviceProtection_Yes NA NA NA NA
TechSupport_No 1.323e-01 2.148e-01 0.616 0.537858
TechSupport_Yes NA NA NA NA
StreamingTV_No -7.836e-01 3.912e-01 -2.003 0.045192 *
StreamingTV_Yes NA NA NA NA
StreamingMovies_No -7.989e-01 3.905e-01 -2.046 0.040777 *
StreamingMovies_Yes NA NA NA NA
Contract_Month.to.month 1.271e+00 1.989e-01 6.391 1.64e-10 ***
Contract_One.year 6.094e-01 2.002e-01 3.044 0.002335 **
Contract_Two.year NA NA NA NA
PaperlessBilling_No -4.228e-01 8.977e-02 -4.710 2.48e-06 ***
PaperlessBilling_Yes NA NA NA NA
PaymentMethod_Bank.transfer..automatic. 7.921e-02 1.381e-01 0.573 0.566389
PaymentMethod_Credit.card..automatic. -4.723e-02 1.405e-01 -0.336 0.736698
PaymentMethod_Electronic.check 4.104e-01 1.157e-01 3.549 0.000387 ***
PaymentMethod_Mailed.check NA NA NA NA
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 5707.1 on 4930 degrees of freedom
Residual deviance: 4085.4 on 4907 degrees of freedom
AIC: 4133.4
Number of Fisher Scoring iterations: 6
Let;s build a regression model using these variables and check how they predict churn.
training.set.2 <- training.set %>%
mutate(Churn_yes = as.factor(ifelse(Churn_yes=="Yes",1,0)))
Totalvalidation.set.2 <- Totalvalidation.set %>%
mutate(Churn_yes = as.factor(ifelse(Churn_yes=="Yes",1,0)))
control_reg <- trainControl(method = "repeatedcv", number = 10, repeats = 3)
set.seed(10)
reg_fit <- train(Churn_yes ~ tenure+
TotalCharges+
Contract_Month.to.month+
PaperlessBilling_No+
PaymentMethod_Electronic.check+
Contract_One.year,
data = training.set.2,
method = "glm",
family = "binomial",
preProcess = c("center","scale"),
trControl = control_reg)
As we can see the accuracy level has dropped to 77%.
print(reg_fit)
Generalized Linear Model
4931 samples
6 predictor
2 classes: '0', '1'
Pre-processing: centered (6), scaled (6)
Resampling: Cross-Validated (10 fold, repeated 3 times)
Summary of sample sizes: 4437, 4438, 4437, 4438, 4439, 4438, ...
Resampling results:
Accuracy Kappa
0.7838158 0.4029077
pred_test_reg <- predict(reg_fit,newdata = Totalvalidation.set.2)
confusionMatrix(data=pred_test_reg, Totalvalidation.set.2$Churn_yes)
Confusion Matrix and Statistics
Reference
Prediction 0 1
0 1383 307
1 169 253
Accuracy : 0.7746
95% CI : (0.7562, 0.7923)
No Information Rate : 0.7348
P-Value [Acc > NIR] : 1.437e-05
Kappa : 0.3722
Mcnemar's Test P-Value : 3.399e-10
Sensitivity : 0.8911
Specificity : 0.4518
Pos Pred Value : 0.8183
Neg Pred Value : 0.5995
Prevalence : 0.7348
Detection Rate : 0.6548
Detection Prevalence : 0.8002
Balanced Accuracy : 0.6714
'Positive' Class : 0
results_2 <- confusionMatrix(data=pred_test_reg, Totalvalidation.set.2$Churn_yes)
confusion_mat2 <- as.data.frame(results_2$table)
ggplot(data = confusion_mat2, mapping = aes(x = Reference, y = Prediction)) +
ggtitle("Confusion Matrix for Regression Iteration 1") +
geom_tile(aes(fill = Freq), colour = "white") +
geom_text(aes(label = sprintf("%1.0f", Freq)), vjust = 1) +
scale_fill_gradient(low = "#B2DBD5", high = "#2B616D") +
theme_bw() + theme(legend.position = "none", plot.title = element_text(hjust = 0.5, face = "bold"))

Let’s try to run regression using the same variables that we used in Random Forest and check the results
control_reg_2 <- trainControl(method = "repeatedcv", number = 10, repeats = 3)
set.seed(10)
reg_fit_2 <- train(Churn_yes ~ tenure+
MonthlyCharges+
TotalCharges+
Contract_Month.to.month+
InternetService_Fiber.optic,
data = training.set.2,
method = "glm",
family = "binomial",
preProcess = c("center","scale"),
trControl = control_reg)
As we can see the accuracy level is still 77%. And the false negatives are still on the higher side.
print(reg_fit_2)
Generalized Linear Model
4931 samples
5 predictor
2 classes: '0', '1'
Pre-processing: centered (5), scaled (5)
Resampling: Cross-Validated (10 fold, repeated 3 times)
Summary of sample sizes: 4438, 4439, 4438, 4438, 4438, 4438, ...
Resampling results:
Accuracy Kappa
0.7864541 0.4129474
pred_test_reg_2 <- predict(reg_fit_2,newdata = Totalvalidation.set.2)
confusionMatrix(data=pred_test_reg, Totalvalidation.set.2$Churn_yes)
Confusion Matrix and Statistics
Reference
Prediction 0 1
0 1383 307
1 169 253
Accuracy : 0.7746
95% CI : (0.7562, 0.7923)
No Information Rate : 0.7348
P-Value [Acc > NIR] : 1.437e-05
Kappa : 0.3722
Mcnemar's Test P-Value : 3.399e-10
Sensitivity : 0.8911
Specificity : 0.4518
Pos Pred Value : 0.8183
Neg Pred Value : 0.5995
Prevalence : 0.7348
Detection Rate : 0.6548
Detection Prevalence : 0.8002
Balanced Accuracy : 0.6714
'Positive' Class : 0
results_3 <- confusionMatrix(data=pred_test_reg, Totalvalidation.set.2$Churn_yes)
confusion_mat3 <- as.data.frame(results_3$table)
ggplot(data = confusion_mat3, mapping = aes(x = Reference, y = Prediction)) +
ggtitle("Confusion Matrix for Regression Iteration 2") +
geom_tile(aes(fill = Freq), colour = "white") +
geom_text(aes(label = sprintf("%1.0f", Freq)), vjust = 1) +
scale_fill_gradient(low = "#B2DBD5", high = "#2B616D") +
theme_bw() + theme(legend.position = "none", plot.title = element_text(hjust = 0.5, face = "bold"))

LS0tDQp0aXRsZTogIkFzc2lnbm1lbnQzIC0gUHJlZGljdCBDdXN0b21lciBDaHVybiINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiMjI0ludHJvZHVjdGlvbg0KDQotLS0NCg0KVGhlIGFpbSBvZiB0aGlzIGFuYWx5c2lzIGlzIHRvIHByZWRpY3QgY3VzdG9tZXIgY2h1cm4gaW4gYSBUZWxlY29tIGNvbXBhbnkuIFR3byBjbGFzc2lmaWNhdGlvbiBtb2RlbHMgd2lsbCBiZSB1c2VkIHRvIHByZWRpY3QgdGhlIGxpa2VseWhvb2Qgb2YgY3VzdG9tZXIgY2h1cm4uIFRoZSB0d28gbW9kZWxzIHdpbGwgYmUgY29tcGFyZWQgYmFzZWQgb24gdGhlaXIgYWNjdXJhY3kuDQpgYGB7ciBpbmNsdWRlID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQojVGhlIFIgbGlicmFyaWVzIGxpc3RlZCBiZWxvdyBhcmUgdXNlZCBmb3IgdGhpcyBhbmFseXNpcy4gVGhpcyBibG9jayBsb2FkcyB0aGUgbGlicmFyaWVzIGFuZCByZWFkcyBkYXRhIGZyb20gdGhlIGZpbGUNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShEYXRhRXhwbG9yZXIpDQpsaWJyYXJ5KHBsb3RseSkNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkNCmxpYnJhcnkoZ3JpZEV4dHJhKQ0KI3JlYWQgZGF0YSBmaWxlIGZyb20gcHJvamVjdCBkYXRhIGZvbGRlciBwcmVzZW50IGluIHByb2plY3Qgd29ya2luZyBkaXJlY3RvcnkNCnRlbGNvX2N1c3RvbWVyX2NodXJuX3Jhd19kYXRhIDwtIHJlYWRfY3N2KCJkYXRhXFxXQV9Gbi1Vc2VDXy1UZWxjby1DdXN0b21lci1DaHVybi5jc3YiKQ0KYGBgDQoNCiMjI0RhdGEgU2V0DQoNCi0tLQ0KDQpUaGUgZGF0YSBzZXQgY29uc2lzdHMgb2YgNywwNDMgb2JzZXJ2YXRpb25zIG9mIDIxIHZhcmlhYmxlcyBhcyBzaG93biBiZWxvdzogPGJyPg0KDQoqICoqY3VzdG9tZXJJRDoqKiBVbmlxdWUgaWRlbnRpZmllciBmb3IgZWFjaCBjdXN0b21lcg0KKiAqKmdlbmRlcjoqKiBHZW5kZXIgb2YgY3VzdG9tZXINCiogKipTZW5pb3JDaXRpemVuOioqIEEgYmluYXJ5IHZhcmlhYmxlIHdoaWNoIGlkZW50aWZpZXMgaWYgYSBjdXN0b21lciBpcyBhIFNlbmlvciBDaXRpemVuICgxKSBvciBub3QgKDApDQoqICoqUGFydG5lcjoqKiBBIGJpbmFyeSB2YXJpYWJsZSB3aGljaCBpZGVudGlmaWVzIGlmIGEgY3VzdG9tZXIgaGFzIGEgcGFydG5lciAoWWVzL05vKQ0KKiAqKkRlcGVuZGVudHM6KiogQSBiaW5hcnkgdmFyaWFibGUgd2hpY2ggaWRlbnRpZmllcyBpZiBhIGN1c3RvbWVyIGhhcyBhbnkgZGVwZW5kZW50cyAoWWVzL05vKQ0KKiAqKnRlbnVyZToqKiBUaGUgdGVudXJlIG9mIGFzc29jaWF0aW9uIG9mIHRoZSBjdXN0b21lciB3aXRoIHRoZSBjb21wYW55IGluIG1vbnRocw0KKiAqKlBob25lU2VydmljZToqKiBBIGJpbmFyeSB2YXJpYWJsZSB3aGljaCBpZGVudGlmaWVzIGlmIGEgY3VzdG9tZXIgaGFzIHBob25lIHNlcnZpY2UNCiogKipNdWx0aXBsZUxpbmVzOioqIFZhcmlhYmxlIHRoYXQgaWRlbnRmaWVzIGlmIGN1c3RvbWVycyBoYXZpbmcgcGhvbmUgbGluZXMgaGF2ZSBtdWx0aXBsZSBsaW5lcyBvciBub3QNCiogKipJbnRlcm5ldFNlcnZpY2U6KiogVGhpcyB2YXJpYWJsZSBpZGVudGlmaWVzIHRoZSB0eXBlIG9mIGludGVybmV0IHNlcnZpY2UgdGhhdCB0aGUgY3VzdG9tZXIgaGFzDQoqICoqT25saW5lU2VjdXJpdHk6KiogVmFyaWFibGUgdGhhdCBpZGVudGZpZXMgaWYgY3VzdG9tZXJzIGhhdmluZyBpbnRlcm5ldCBzZXJ2aWNlIGhhdmUgb25saW5lIHNlY3VyaXR5IG9yIG5vdA0KKiAqKk9ubGluZWJhY2t1cDoqKiBWYXJpYWJsZSB0aGF0IGlkZW50ZmllcyBpZiBjdXN0b21lcnMgaGF2aW5nIGludGVybmV0IHNlcnZpY2UgaGF2ZSBvbmxpbmUgYmFja3VwIG9yIG5vdA0KKiAqKkRldmljZVByb3RlY3Rpb246KiogVmFyaWFibGUgdGhhdCBpZGVudGZpZXMgaWYgY3VzdG9tZXJzIGhhdmluZyBpbnRlcm5ldCBzZXJ2aWNlIGhhdmUgZGV2aWNlIHByb3RlY3Rpb24gb3Igbm90DQoqICoqVGVjaFN1cHBvcnQ6KiogVmFyaWFibGUgdGhhdCBpZGVudGZpZXMgaWYgY3VzdG9tZXJzIGhhdmluZyBpbnRlcm5ldCBzZXJ2aWNlIGhhdmUgYXZhaWxlZCB0ZWNoIHN1cHBvcnQgb3Igbm90DQoqICoqU3RyZWFtaW5nVFY6KiogVmFyaWFibGUgdGhhdCBpZGVudGZpZXMgaWYgY3VzdG9tZXJzIGhhdmluZyBpbnRlcm5ldCBzZXJ2aWNlIGhhdmUgVFYgU3RyZWFtaW5nIG9yIG5vdA0KKiAqKlN0cmVhbWluZ01vdmllczoqKiBWYXJpYWJsZSB0aGF0IGlkZW50ZmllcyBpZiBjdXN0b21lcnMgaGF2aW5nIGludGVybmV0IHNlcnZpY2UgaGF2ZSBNb3ZpZSBTdHJlYW1pbmcgb3Igbm90DQoqICoqQ29udHJhY3Q6KiogVmFyaWFibGUgdGhhdCBpZGVudGZpZXMgdGhlIGNvbnRyYWN0IHR5cGUgb2YgdGhlIGN1c3RvbWVycw0KKiAqKlBhcGVybGVzc0JpbGxpbmc6KiogVmFyaWFibGUgdGhhdCBpZGVudGZpZXMgaWYgdGhlIGN1c3RvbWVyIGhhcyBvcHRlZCBmb3IgcGFwZXJsZXNzIGJpbGxpbmcNCiogKipQYXltZW50TWV0aG9kOioqIFZhcmlhYmxlIHRoYXQgaWRlbnRmaWVzIHRoZSBwYXltZW50IG1ldGhvZCBvZiB0aGUgY3VzdG9tZXINCiogKipNb250aGx5Q2hhcmdlczoqKiBWYXJpYWJsZSB0aGF0IGlkZW50ZmllcyB0aGUgbW9udGhseSBiaWxsIGFtb3VudCBvZiB0aGUgY3VzdG9tZXINCiogKipUb3RhbENoYXJnZXM6KiogVmFyaWFibGUgdGhhdCBpZGVudGZpZXMgdGhlIG92ZXJhbGwgYmlsbCBhbW91bnQgb2YgdGhlIGN1c3RvbWVyIHRocm91Z2hvdXQgdGhlIHRlbnVyZQ0KKiAqKkNodXJuOioqIFZhcmlhYmxlIHRoYXQgaWRlbnRmaWVzIGlmIHRoZSBjdXN0b21lciBoYXMgY2FuY2VsbGVkIHRoZSBjb21wYW55J3Mgc2VydmljZXM8YnI+DQoNCkxldCdzIGxvb2sgYXQgdGhlIGRpc3RpbmN0IHZhbHVlcyBwcmVzZW50IGluIGVhY2ggY2F0ZWdvcmljYWwgdmFyaWFibGUuDQoNClNvbWUgZmFjdG9yIHZhcmlhYmxlcyBoYXZlIDMgdHlwZXMsIGxldCdzIHNlZSB3aGF0IHRob3NlIHZhbHVlcyBhcmUsIGFuZCBpZiB3ZSBjYW4gZ3JvdXAgdGhlbSBpbiBhbnl3YXkuDQpgYGB7ciBtZXNzYWdlPUZBTFNFfQ0Kb3B0aW9ucyh3aWR0aCA9IDEwKQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fcmF3X2RhdGEgJT4lDQogICAgIHN1bW1hcmlzZV9hbGwoZnVucyhuX2Rpc3RpbmN0KC4pKSkNCg0KYXMuZGF0YS5mcmFtZSh0YWJsZSh0ZWxjb19jdXN0b21lcl9jaHVybl9yYXdfZGF0YSRNdWx0aXBsZUxpbmVzKSkNCmFzLmRhdGEuZnJhbWUodGFibGUodGVsY29fY3VzdG9tZXJfY2h1cm5fcmF3X2RhdGEkSW50ZXJuZXRTZXJ2aWNlKSkNCmFzLmRhdGEuZnJhbWUodGFibGUodGVsY29fY3VzdG9tZXJfY2h1cm5fcmF3X2RhdGEkT25saW5lU2VjdXJpdHkpKQ0KYXMuZGF0YS5mcmFtZSh0YWJsZSh0ZWxjb19jdXN0b21lcl9jaHVybl9yYXdfZGF0YSRPbmxpbmVCYWNrdXApKQ0KYXMuZGF0YS5mcmFtZSh0YWJsZSh0ZWxjb19jdXN0b21lcl9jaHVybl9yYXdfZGF0YSREZXZpY2VQcm90ZWN0aW9uKSkNCmFzLmRhdGEuZnJhbWUodGFibGUodGVsY29fY3VzdG9tZXJfY2h1cm5fcmF3X2RhdGEkVGVjaFN1cHBvcnQpKQ0KYXMuZGF0YS5mcmFtZSh0YWJsZSh0ZWxjb19jdXN0b21lcl9jaHVybl9yYXdfZGF0YSRTdHJlYW1pbmdNb3ZpZXMpKQ0KYXMuZGF0YS5mcmFtZSh0YWJsZSh0ZWxjb19jdXN0b21lcl9jaHVybl9yYXdfZGF0YSRTdHJlYW1pbmdUVikpDQpgYGAgICAgICANCkFzIHdlIGNhbiBzZWUsIHNvbWUgb2YgdGhlIGZhY3RvcnMgaGF2ZSAzIHZhbHVlcywgYnV0IHRoZSBjYXRlZ29yaWVzICJObyBJbnRlcm5ldCBTZXJ2aWNlIi8iTm8gUGhvbmUgU2VydmljZSIgYXJlIG5vdCBhZGRpbmcgYW55IGFkZGl0aW9uYWwgdmFsdWUgYXMgdGhhdCBpbmZvcm1hdGlvbiBpcyBhbHJlYWR5IGNhcHR1cmVkIGluIGEgc2VwYXJhdGUgdmFyaWFibGUuIFNvLCBsZXQncyB1cGRhdGUgdGhlc2UgdHdvIHZhbHVlcyB0byAiTm8iLg0KDQpBbHNvLCBzaW5jZSBhbGwgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGFyZSBpbiB5ZXMvbm8gZm9ybWF0IGV4Y2VwdCBTZW5pb3IgQ2l0aXplbiwgd2UgY2FuIHRyYW5zZm9ybSB0aGF0IGFsc28gaW50byBZZXMvTm8gZm9ybWF0LiBMZXQncyBhbHNvIHRyYW5zZm9ybSB0aGUgb3V0cHV0IHZhcmlhYmxlIENodXJuIGludG8gMS8wIGZvcm1hdCBzbyB0aGF0IHdlIGRvIG5vdCBoYXZlIHRvIGR1bW1pZnkgaXQgZnVydGhlci4NCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQojVHJhbnNmb3JtIE11bHRpcGxlTGluZXMgVmFyaWFibGUNCnRlbGNvX2N1c3RvbWVyX2NodXJuX3RyYW5zZm9ybWVkX2RhdGEgPC0gdGVsY29fY3VzdG9tZXJfY2h1cm5fcmF3X2RhdGEgJT4lDQogICAgIG11dGF0ZShNdWx0aXBsZUxpbmVzID0gaWZlbHNlKE11bHRpcGxlTGluZXM9PSJObyBwaG9uZSBzZXJ2aWNlIiwiTm8iLE11bHRpcGxlTGluZXMpKQ0KI1RyYW5zZm9ybSBPbmxpbmVTZWN1cml0eSBWYXJpYWJsZQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl90cmFuc2Zvcm1lZF9kYXRhICU+JQ0KICAgICBtdXRhdGUoT25saW5lU2VjdXJpdHkgPSBpZmVsc2UoT25saW5lU2VjdXJpdHk9PSJObyBpbnRlcm5ldCBzZXJ2aWNlIiwiTm8iLE9ubGluZVNlY3VyaXR5KSkNCiNUcmFuc2Zvcm0gT25saWxuZUJhY2t1cCBWYXJpYWJsZQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl90cmFuc2Zvcm1lZF9kYXRhICU+JQ0KICAgICBtdXRhdGUoT25saW5lQmFja3VwID0gaWZlbHNlKE9ubGluZUJhY2t1cD09Ik5vIGludGVybmV0IHNlcnZpY2UiLCJObyIsT25saW5lQmFja3VwKSkNCiNUcmFuc2Zvcm0gRGV2aWNlUHJvdGVjdGlvbiBWYXJpYWJsZQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl90cmFuc2Zvcm1lZF9kYXRhICU+JQ0KICAgICBtdXRhdGUoRGV2aWNlUHJvdGVjdGlvbiA9IGlmZWxzZShEZXZpY2VQcm90ZWN0aW9uPT0iTm8gaW50ZXJuZXQgc2VydmljZSIsIk5vIixEZXZpY2VQcm90ZWN0aW9uKSkNCiNUcmFuc2Zvcm0gVGVjaFN1cHBvcnQgVmFyaWFibGUNCnRlbGNvX2N1c3RvbWVyX2NodXJuX3RyYW5zZm9ybWVkX2RhdGEgPC0gdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSAlPiUNCiAgICAgbXV0YXRlKFRlY2hTdXBwb3J0ID0gaWZlbHNlKFRlY2hTdXBwb3J0PT0iTm8gaW50ZXJuZXQgc2VydmljZSIsIk5vIixUZWNoU3VwcG9ydCkpDQojVHJhbnNmb3JtIFN0cmVhbWluZyBNb3ZpZXMgVmFyaWFibGUNCnRlbGNvX2N1c3RvbWVyX2NodXJuX3RyYW5zZm9ybWVkX2RhdGEgPC0gdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSAlPiUNCiAgICAgbXV0YXRlKFN0cmVhbWluZ01vdmllcyA9IGlmZWxzZShTdHJlYW1pbmdNb3ZpZXM9PSJObyBpbnRlcm5ldCBzZXJ2aWNlIiwiTm8iLFN0cmVhbWluZ01vdmllcykpDQojVHJhbnNmb3JtIFN0cmVhbWluZyBUViBWYXJpYWJsZQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl90cmFuc2Zvcm1lZF9kYXRhICU+JQ0KICAgICBtdXRhdGUoU3RyZWFtaW5nVFYgPSBpZmVsc2UoU3RyZWFtaW5nVFY9PSJObyBpbnRlcm5ldCBzZXJ2aWNlIiwiTm8iLFN0cmVhbWluZ1RWKSkNCiNUcmFuc2Zvcm0gU2VuaW9yIENpdGl6ZW4gVmFyaWFibGUNCnRlbGNvX2N1c3RvbWVyX2NodXJuX3RyYW5zZm9ybWVkX2RhdGEgPC0gdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSAlPiUNCiAgICAgbXV0YXRlKFNlbmlvckNpdGl6ZW4gPSBpZmVsc2UoU2VuaW9yQ2l0aXplbj09MSwiWWVzIiwiTm8iKSkNCiNUcmFuc2Zvcm0gQ2h1cm4gVmFyaWFibGUNCnRlbGNvX2N1c3RvbWVyX2NodXJuX3RyYW5zZm9ybWVkX2RhdGEgPC0gdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSAlPiUNCiAgICAgbXV0YXRlKENodXJuID0gaWZlbHNlKENodXJuPT0iWWVzIiwxLDApKQ0KDQpgYGANCg0KTGV0J3MgbG9vayBhdCB0aGUgc3RydWN0dXJlIG9mIHRoZSBkYXRhc2V0Lg0KYGBge3IgZmlnLndpZHRoPTgsICBtZXNzYWdlPUZBTFNFfQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSAlPiUNCiAgICAgcGxvdF9zdHIoKQ0KYGBgDQpGcm9tIHRoZSBhYm92ZSBncmFwaCB3ZSBjYW4gbm90aWNlIHRoYXQgc29tZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgYXJlIGN1cnJlbnRseSAnY2hhcmFjdGVycycuIFdlIGNhbiBjb252ZXJ0IHRoZW0gdG8gZmFjdG9ycyBmb3IgbW9yZSBhY2N1cmF0ZSByZXByZXNlbnRhdGlvbi4NCmBgYHtyICBtZXNzYWdlPUZBTFNFfQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl90cmFuc2Zvcm1lZF9kYXRhICU+JQ0KICAgICBtdXRhdGUoU2VuaW9yQ2l0aXplbiA9IGFzLmZhY3RvcihTZW5pb3JDaXRpemVuKSwNCiAgICAgICAgICAgIGdlbmRlciA9IGFzLmZhY3RvcihnZW5kZXIpLA0KICAgICAgICAgICAgUGFydG5lciA9IGFzLmZhY3RvcihQYXJ0bmVyKSwNCiAgICAgICAgICAgIERlcGVuZGVudHMgPSBhcy5mYWN0b3IoRGVwZW5kZW50cyksDQogICAgICAgICAgICBTZW5pb3JDaXRpemVuID0gYXMuZmFjdG9yKFNlbmlvckNpdGl6ZW4pLA0KICAgICAgICAgICAgUGhvbmVTZXJ2aWNlID0gYXMuZmFjdG9yKFBob25lU2VydmljZSksDQogICAgICAgICAgICBNdWx0aXBsZUxpbmVzID0gYXMuZmFjdG9yKE11bHRpcGxlTGluZXMpLA0KICAgICAgICAgICAgSW50ZXJuZXRTZXJ2aWNlID0gYXMuZmFjdG9yKEludGVybmV0U2VydmljZSksDQogICAgICAgICAgICBPbmxpbmVTZWN1cml0eSA9IGFzLmZhY3RvcihPbmxpbmVTZWN1cml0eSksDQogICAgICAgICAgICBPbmxpbmVCYWNrdXAgPSBhcy5mYWN0b3IoT25saW5lQmFja3VwKSwNCiAgICAgICAgICAgIERldmljZVByb3RlY3Rpb24gPSBhcy5mYWN0b3IoRGV2aWNlUHJvdGVjdGlvbiksDQogICAgICAgICAgICBUZWNoU3VwcG9ydCA9IGFzLmZhY3RvcihUZWNoU3VwcG9ydCksDQogICAgICAgICAgICBTdHJlYW1pbmdUViA9IGFzLmZhY3RvcihTdHJlYW1pbmdUViksDQogICAgICAgICAgICBTdHJlYW1pbmdNb3ZpZXMgPSBhcy5mYWN0b3IoU3RyZWFtaW5nTW92aWVzKSwNCiAgICAgICAgICAgIFBhcGVybGVzc0JpbGxpbmcgPSBhcy5mYWN0b3IoUGFwZXJsZXNzQmlsbGluZyksDQogICAgICAgICAgICBDb250cmFjdCA9IGFzLmZhY3RvcihDb250cmFjdCksDQogICAgICAgICAgICBQYXltZW50TWV0aG9kID0gYXMuZmFjdG9yKFBheW1lbnRNZXRob2QpLA0KICAgICAgICAgICAgQ2h1cm4gPSBhcy5mYWN0b3IoQ2h1cm4pKQ0KYGBgDQoNCiMjI0RhdGEgRXhwbG9yYXRpb24NCg0KLS0tDQoNCkFzIHRoZSBncmFwaCBiZWxvdyBzaG93cywgOTkuOCUgb2YgdGhlIG9ic2VydmF0aW9ucyBhcmUgY29tcGxldGUuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQp0ZWxjb19jdXN0b21lcl9jaHVybl90cmFuc2Zvcm1lZF9kYXRhICU+JQ0KICAgICBwbG90X2ludHJvKCkNCmBgYA0KVGhlIGJlbG93IGdyYXBoIHNob3dzIHVzIHRoYXQgIlRvdGFsQ2hhcmdlcyIgaGFzIG1pc3NpbmcgdmFsdWVzLiBUaG91Z2ggdGhleSBhcmUgdmVyeSBmZXcsIHdlIHdpbGwgY2hlY2sgdGhlIHJlYXNvbiBiZWhpbmQgdGhpcyBpbiBmdXJ0aGVyIHNlY3Rpb25zLg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCnRlbGNvX2N1c3RvbWVyX2NodXJuX3RyYW5zZm9ybWVkX2RhdGEgJT4lDQogICAgIHBsb3RfbWlzc2luZyh0aXRsZSA9ICJNaXNzaW5nIHZhbHVlcyBpbiB2YXJpYWJsZXMiKQ0KYGBgDQoNCkZyb20gdGhlIGJlbG93IGNvdW50IHdlIGNhbiBzZWUgdGhhdCB0aGVyZSBhcmUgbm8gZHVwbGljYXRlIEN1c3RvbWVyIElEcyBpbiB0aGUgZGF0YXNldC4gVGhlcmUgYXJlIDcwNDMgcmVjb3JkcyBhbmQgNzA0MyB1bmlxdWUgQ3VzdG9tZXIgSURzDQpgYGB7cn0NCnRlbGNvX2N1c3RvbWVyX2NodXJuX3RyYW5zZm9ybWVkX2RhdGEgJT4lDQogICAgIHN1bW1hcmlzZShUb3RhbF9OdW1fUmVjb3JkcyA9IG4oKSxVbmlxdWVfQ3VzdG9tZXJJRHMgPSBuX2Rpc3RpbmN0KGN1c3RvbWVySUQpKQ0KYGBgDQoNCkZyb20gdGhlIGJlbG93IGZyZXF1ZW5jeSBkaXN0cmlidXRpb25zIHdlIGNhbiBzZWUgdGhhdCB0aGVyZSBhcmUgbm8gZHVwbGljYXRlIHZhbHVlcyBpbiBhbnkgY2F0ZWdvcmljYWwgdmFyaWFibGUuIFRoZSBkYXRhIGFwcGVhcnMgdG8gYmUgY2xlYW4uDQpgYGB7ciBmaWcud2lkdGg9MTAsIG1lc3NhZ2U9RkFMU0V9DQojQ3VzdG9tZXIgSUQgcmVsYXRlZCBpbmZvcm1hdGlvbiBoYXMgYWxyZWFkeSBiZWVuIGNoZWNrIGFib3ZlLiBJdCBpcyBub3QgcmVxdWlyZWQgaGVyZS4NCnRlbGNvX2N1c3RvbWVyX2NodXJuX3RyYW5zZm9ybWVkX2RhdGEgJT4lDQogICAgIHNlbGVjdCgtY3VzdG9tZXJJRCkgJT4lDQogICAgIHBsb3RfYmFyKGdndGhlbWUgPSB0aGVtZV9saWdodCgpLCB0aXRsZSA9ICJDYXRlZ29yaWNhbCBWYXJpYWJsZSBEaXN0cmlidXRpb25zIikNCg0KP3Bsb3RfYmFyDQpgYGANCg0KRnJvbSB0aGUgYmVsb3cgaGlzdG9ncmFtcyB3ZSBjYW4gc2VlIHRoYXQgdGhlcmUgYXJlIHNvbWUgY3VzdG9tZXJzIHdpdGggYSB0ZW51cmUgb2YgMCBtb250aHMuIFdlIGFzc3VtZSB0aGF0IHRoZXNlIGFyZSBuZXcgY3VzdG9tZXJzLCBhZGRlZCBpbiB0aGUgcGFzdCBtb250aC4NCg0KQWxzbywgbm90aWNlIHRoYXQgVG90YWwgQ2hhcmdlcyBpcyBza2V3ZWQgdG8gdGhlIHJpZ2h0LCB3aGljaCBtYWtlcyBzZW5zZSBhcyB3ZSBoYXZlIGN1c3RvbWVycyB3aXRoIGhpZ2ggdGVudXJlLiBUaGUgdG90YWwgY2hhcmdlcyBmb3IgdGhlbSB3aWxsIGhhdmUgYWNjdW11bGF0ZWQgZm9yIGEgbG9uZyBwZXJpb2QuDQpgYGB7ciBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD02fQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSAlPiUNCiAgICAgcGxvdF9oaXN0b2dyYW0oZ2d0aGVtZSA9IHRoZW1lX2xpZ2h0KCkpDQpgYGANCg0KVGhlIG1pc3NpbmcgdmFsdWVzIG9mICJUb3RhbENoYXJnZXMiIHRoYXQgd2Ugbm90aWNlZCBpbiBlYXJsaWVyIHNlY3Rpb25zIGlzIGJlY2F1c2Ugb2YgdGhlIG5ldyBjdXN0b21lcnMgKHRlbnVyZT0wKS4gU2luY2UgdGhleSBoYXZlIG5vdCBjb21wbGV0ZWQgYSBiaWxsaW5nIGN5Y2xlIHlldCwgdGhlaXIgVG90YWwgQ2hhcmdlIHZhbHVlIGlzIG1pc3NpbmcuIFdlIGNhbiBjb25maXJtIHRoaXMgd2l0aCB0aGUgcmVzdWx0IGJlbG93LCB3aGljaCBjaGVja3MgaWYgdGhlcmUgYXJlIGFueSBtaXNzaW5nIFRvdGFsQ2hhcmdlcyB2YWx1ZXMgZm9yIG9ic2VydmF0aW9ucyB3aXRoIG5vbi16ZXJvIHRlbnVyZXMuIEFzIHdlIGNhbiBzZWUgdGhlcmUgYXJlIG5vbmUuIFNvLCB3ZSBjYW4gY29uY2x1ZGUgdGhhdCBtaXNzaW5nIFRvdGFsQ2hhcmdlcyBhcmUgYmVjYXVzZSBvZiAwIHRlbnVyZS4NCmBgYHtyfQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fdHJhbnNmb3JtZWRfZGF0YSAlPiUNCiAgICAgZmlsdGVyKGlzLm5hKFRvdGFsQ2hhcmdlcykgJiYgdGVudXJlIT0wKQ0KYGBgDQoNCkxldCdzIGNvbnZlcnQgbWlzc2luZyBUb3RhbENoYXJnZXMgdG8gMCBhbmQgcGxvdCBtaXNzaW5nIHZhbHVlcyBhZ2Fpbi4NCmBgYHtyfQ0KI0NoZWNrIGlmIFRvdGFsQ2hhcmdlcyBpcyBOQSBhbmQgcmVwbGFjZSB3aXRoIDANCnRlbGNvX2N1c3RvbWVyX2NodXJuX2NsZWFuZWRfZGF0YSA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl90cmFuc2Zvcm1lZF9kYXRhICU+JQ0KICAgICBtdXRhdGUoVG90YWxDaGFyZ2VzID0gaWZlbHNlKGlzLm5hKFRvdGFsQ2hhcmdlcyksMCxUb3RhbENoYXJnZXMpKQ0KDQp0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgIHBsb3RfbWlzc2luZygpDQpgYGANCkJlZm9yZSBwcm9jZWVkaW5nIHRvIG1vZGVsaW5nLCBsZXQncyBjaGVjayBpZiB0aGVyZSBhcmUgYW55IHRyZW5kcyBpbiB0aGUgZGF0YS4gDQoNClRoZSBvdmVyYWxsIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBpbiB0aGlzIGRhdGEgc2V0IGNhbiBiZSBjbGFzc2lmaWVkIGFzIGZvbGxvd3M6DQoNCiogdmFyaWFibGVzIHRoYXQgZGVmaW5lIGF0dHJpYnV0ZXMgb2YgYSBjdXN0b21lcg0KKiB2YXJpYWJsZXMgdGhhdCBkZWZpbmUgYXR0cmlidXRlcyBvZiB0aGUgc2VydmljZQ0KDQpMZXQncyBjaGVjayBob3cgdGhlc2UgdmFyaWFibGVzIGFmZmVjdCBDaHVybiBieSBwbG90dGluZyB0aGVtDQoNCioqQ3VzdG9tZXIgQXRycmlidXRlcyoqDQoNCi0tLQ0KDQpGcm9tIHRoZSBncmFwaHMgYmVsb3cgd2UgY2FuIG5vdGljZSB0aGF0Og0KDQoqIEdlbmRlciBkb2VzIG5vdCBoYXZlIGEgY2xlYXIgdHJlbmQgZm9yIENodXJuOyB0aGUgZ2VuZGVyIHNwbGl0IG9mIGNodXJuZWQgY3VzdG9tZXJzIGlzIG5lYXJseSA1MC01MC4NCiogU2VuaW9yIENpdGl6ZW5zIGFuZCBjdXN0b21lcnMgd2l0aCBkZXBlbmRlbnRzIHNlZW1zIHRvIGhhdmUgbG93ZXIgY2h1cm4sIHdlIGNhbiBjaGVjayB0aGUgZWZmZWN0IG9mIHRoZXNlIHZhcmlhYmxlIG9uIGNodXJuIGZ1cnRoZXIuDQpgYGB7cn0NCiN1c2luZyBwbG90bHkgaGVyZSBqdXN0IHRvIGV4cGxvcmUgdGhlIGZ1bmN0aW5hbGl0aWVzIGluIHRoaXMgcGFja2FnZSBtb3JlDQojUGxvdCBieSBTZW5pb3JDaXRpemVuDQp0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgIGdyb3VwX2J5KENodXJuLFNlbmlvckNpdGl6ZW4pICU+JQ0KICAgICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JQ0KICAgICBwbG90X2x5KHg9IH5TZW5pb3JDaXRpemVuLCB5PSB+Y291bnQsIG5hbWU9IH5DaHVybiwgdHlwZSA9ICdiYXInLCB3aWR0aCA9IDQwMCwgaGVpZ2h0ID0gNDAwKSAlPiUNCiAgICAgbGF5b3V0KHRpdGxlID0gJ1NlbmlvciBDaXRpemVuIGRpc3RyaWJ1dGlvbiBiYXNlZCBvbiBDaHVybicpDQoNCiNQbG90IGJ5IEdlbmRlcg0KdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICBncm91cF9ieShDaHVybixnZW5kZXIpICU+JQ0KICAgICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JQ0KICAgICBwbG90X2x5KHg9IH5nZW5kZXIsIHk9IH5jb3VudCwgbmFtZT0gfkNodXJuLCB0eXBlID0gJ2JhcicsIHdpZHRoID0gNDAwLCBoZWlnaHQgPSA0MDApICU+JQ0KICAgICBsYXlvdXQodGl0bGUgPSAnR2VuZGVyIGRpc3RyaWJ1dGlvbiBiYXNlZCBvbiBDaHVybicpDQoNCiNQbG90IGJ5IERlcGVuZGVudHMNCnRlbGNvX2N1c3RvbWVyX2NodXJuX2NsZWFuZWRfZGF0YSAlPiUNCiAgICAgZ3JvdXBfYnkoQ2h1cm4sRGVwZW5kZW50cykgJT4lDQogICAgIHN1bW1hcmlzZShjb3VudCA9IG4oKSkgJT4lDQogICAgIHBsb3RfbHkoeD0gfkRlcGVuZGVudHMsIHk9IH5jb3VudCwgbmFtZT0gfkNodXJuLCB0eXBlID0gJ2JhcicsIHdpZHRoID0gNDAwLCBoZWlnaHQgPSA0MDApICU+JQ0KICAgICBsYXlvdXQodGl0bGUgPSAnRGVwZW5kZW50IGRpc3RyaWJ1dGlvbiBiYXNlZCBvbiBDaHVybicpDQogICAgDQojUGxvdCBieSBQYXJ0bmVyDQp0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgIGdyb3VwX2J5KENodXJuLFBhcnRuZXIpICU+JQ0KICAgICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JQ0KICAgICBwbG90X2x5KHg9IH5QYXJ0bmVyLCB5PSB+Y291bnQsIG5hbWU9IH5DaHVybiwgdHlwZSA9ICdiYXInLCB3aWR0aCA9IDQwMCwgaGVpZ2h0ID0gNDAwKSAlPiUNCiAgICAgbGF5b3V0KHRpdGxlID0gJ1BhcnRuZXIgZGlzdHJpYnV0aW9uIGJhc2VkIG9uIENodXJuJykNCg0KYGBgDQoNCioqU2VydmljZSBBdHJyaWJ1dGVzKioNCg0KLS0tDQoNCkZyb20gdGhlIHBsb3RzIGJlbG93IHdlIGNhbiBpbmZlciB0aGUgZm9sbG93aW5nOg0KDQoqIEN1c3RvbWVycyB3aXRoIEZpYmVyIE9wdGljIEludGVybmV0IFNldmljZSBzZWVtcyB0byBjaHVybiBtb3JlDQoqIEN1c3RvbWVyIHdpdGggTW9udGgtdG8tbW9udGggY29udHJhY3RzIHNlZW1zIHRvIGNodXJuIG1vcmUNCiogQ3VzdG9tZXJzIHdobyBwYXkgdGhlaXIgYmlsbHMgdGhyb3VnaCBFbGVjdHJvbmljIENoZWNrIHNlZW0gdG8gY2h1cm4gbW9yZQ0KKiBDdXN0b21lcnMgd2hvIGhhdmUgb3B0ZWQgZm9yIHBhcGVybGVzcyBiaWxscyBzZWVtIHRvIGNodXJuIG1vcmUNCg0KYGBge3IgZmlnLndpZHRoPTE1LCBmaWcuaGVpZ2h0PTEwfQ0KI3VzaW5nIGdncGxvdCBoZXJlIGp1c3QgdG8gZXhwbG9yZSB0aGUgZnVuY3RpbmFsaXRpZXMgaW4gdGhpcyBwYWNrYWdlIG1vcmUNCiNQbG90IGJ5IFNlbmlvckNpdGl6ZW4NCnAxIDwtIHRlbGNvX2N1c3RvbWVyX2NodXJuX2NsZWFuZWRfZGF0YSAlPiUNCiAgICAgZ3JvdXBfYnkoQ2h1cm4sUGhvbmVTZXJ2aWNlKSAlPiUNCiAgICAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgICAgZ2dwbG90KGFlcyhQaG9uZVNlcnZpY2UsY291bnQsZmlsbCA9IENodXJuKSkrDQogICAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9ICJkb2RnZSIpKw0KICAgICBjb29yZF9mbGlwKCkrDQogICAgIGdndGl0bGUoIlBob25lU2VydmljZSBkaXN0cmlidXRpb24iKSArDQogICAgbGFicygNCiAgICAgICAgeCA9ICJQaG9uZSBTZXJ2aWNlIiwNCiAgICAgICAgeSA9ICJGcmVxdWVuY3kiDQogICAgKSArDQogICAgdGhlbWUoDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoDQogICAgICAgICAgICBmaWxsID0gIndoaXRlIiwNCiAgICAgICAgICAgIGNvbG91ciA9ICJsaWdodGdyZXkiLA0KICAgICAgICAgICAgc2l6ZSA9IDAuNzUpLA0KICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAiI0M0REZFNiIsIHNpemUgPSAwLjA1KQ0KICAgICkNCiNQbG90IGJ5IEdlbmRlcg0KcDIgPC0gdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICBncm91cF9ieShDaHVybixNdWx0aXBsZUxpbmVzKSAlPiUNCiAgICAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgICAgZ2dwbG90KGFlcyhNdWx0aXBsZUxpbmVzLGNvdW50LGZpbGwgPSBDaHVybikpKw0KICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZG9kZ2UiKSsNCiAgICAgY29vcmRfZmxpcCgpKw0KICAgICBnZ3RpdGxlKCJNdWx0aXBsZUxpbmVzIGRpc3RyaWJ1dGlvbiIpICsNCiAgICBsYWJzKA0KICAgICAgICB4ID0gIk11bHRpcGxlIExpbmVzIiwNCiAgICAgICAgeSA9ICJGcmVxdWVuY3kiDQogICAgKSArDQogICAgdGhlbWUoDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoDQogICAgICAgICAgICBmaWxsID0gIndoaXRlIiwNCiAgICAgICAgICAgIGNvbG91ciA9ICJsaWdodGdyZXkiLA0KICAgICAgICAgICAgc2l6ZSA9IDAuNzUpLA0KICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAiI0M0REZFNiIsIHNpemUgPSAwLjA1KQ0KICAgICkNCiNQbG90IGJ5IERlcGVuZGVudHMNCnAzIDwtIHRlbGNvX2N1c3RvbWVyX2NodXJuX2NsZWFuZWRfZGF0YSAlPiUNCiAgICAgZ3JvdXBfYnkoQ2h1cm4sSW50ZXJuZXRTZXJ2aWNlKSAlPiUNCiAgICAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgICAgZ2dwbG90KGFlcyhJbnRlcm5ldFNlcnZpY2UsY291bnQsZmlsbCA9IENodXJuKSkrDQogICAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9ICJkb2RnZSIpKw0KICAgICBjb29yZF9mbGlwKCkrDQogICAgIGdndGl0bGUoIkludGVybmV0U2VydmljZSBkaXN0cmlidXRpb24iKSArDQogICAgbGFicygNCiAgICAgICAgeCA9ICJJbnRlcm5ldCBTZXJ2aWNlIiwNCiAgICAgICAgeSA9ICJGcmVxdWVuY3kiDQogICAgKSArDQogICAgdGhlbWUoDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoDQogICAgICAgICAgICBmaWxsID0gIndoaXRlIiwNCiAgICAgICAgICAgIGNvbG91ciA9ICJsaWdodGdyZXkiLA0KICAgICAgICAgICAgc2l6ZSA9IDAuNzUpLA0KICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAiI0M0REZFNiIsIHNpemUgPSAwLjA1KQ0KICAgICkNCiNQbG90IGJ5IE9ubGluZVNlY3VyaXR5DQpwNCA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgIGdyb3VwX2J5KENodXJuLE9ubGluZVNlY3VyaXR5KSAlPiUNCiAgICAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgICAgZ2dwbG90KGFlcyhPbmxpbmVTZWN1cml0eSxjb3VudCxmaWxsID0gQ2h1cm4pKSsNCiAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikrDQogICAgIGNvb3JkX2ZsaXAoKSsNCiAgICAgZ2d0aXRsZSgiT25saW5lU2VjdXJpdHkgZGlzdHJpYnV0aW9uIikgKw0KICAgIGxhYnMoDQogICAgICAgIHggPSAiT25saW5lIFNlY3VyaXR5IiwNCiAgICAgICAgeSA9ICJGcmVxdWVuY3kiDQogICAgKSArDQogICAgdGhlbWUoDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoDQogICAgICAgICAgICBmaWxsID0gIndoaXRlIiwNCiAgICAgICAgICAgIGNvbG91ciA9ICJsaWdodGdyZXkiLA0KICAgICAgICAgICAgc2l6ZSA9IDAuNzUpLA0KICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAiI0M0REZFNiIsIHNpemUgPSAwLjA1KQ0KICAgICkNCiNQbG90IGJ5IE9ubGluZUJhY2t1cA0KcDUgPC0gdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICBncm91cF9ieShDaHVybixPbmxpbmVCYWNrdXApICU+JQ0KICAgICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JQ0KICAgICBnZ3Bsb3QoYWVzKE9ubGluZUJhY2t1cCxjb3VudCxmaWxsID0gQ2h1cm4pKSsNCiAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikrDQogICAgIGNvb3JkX2ZsaXAoKSsNCiAgICAgZ2d0aXRsZSgiT25saW5lQmFja3VwIGRpc3RyaWJ1dGlvbiIpICsNCiAgICBsYWJzKA0KICAgICAgICB4ID0gIk9ubGluZSBCYWNrdXAiLA0KICAgICAgICB5ID0gIkZyZXF1ZW5jeSINCiAgICApICsNCiAgICB0aGVtZSgNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgZmFjZSA9ICJib2xkIiksDQogICAgICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdCgNCiAgICAgICAgICAgIGZpbGwgPSAid2hpdGUiLA0KICAgICAgICAgICAgY29sb3VyID0gImxpZ2h0Z3JleSIsDQogICAgICAgICAgICBzaXplID0gMC43NSksDQogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAid2hpdGUiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9saW5lKGNvbG91ciA9ICIjQzRERkU2Iiwgc2l6ZSA9IDAuMDUpDQogICAgKQ0KI1Bsb3QgYnkgRGV2aWNlUHJvdGVjdGlvbg0KcDYgPC0gdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICBncm91cF9ieShDaHVybixEZXZpY2VQcm90ZWN0aW9uKSAlPiUNCiAgICAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgICAgZ2dwbG90KGFlcyhEZXZpY2VQcm90ZWN0aW9uLGNvdW50LGZpbGwgPSBDaHVybikpKw0KICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZG9kZ2UiKSsNCiAgICAgY29vcmRfZmxpcCgpKw0KICAgICBnZ3RpdGxlKCJEZXZpY2VQcm90ZWN0aW9uIGRpc3RyaWJ1dGlvbiIpICsNCiAgICBsYWJzKA0KICAgICAgICB4ID0gIkRldmljZSBQcm90ZWN0aW9uIiwNCiAgICAgICAgeSA9ICJGcmVxdWVuY3kiDQogICAgKSArDQogICAgdGhlbWUoDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoDQogICAgICAgICAgICBmaWxsID0gIndoaXRlIiwNCiAgICAgICAgICAgIGNvbG91ciA9ICJsaWdodGdyZXkiLA0KICAgICAgICAgICAgc2l6ZSA9IDAuNzUpLA0KICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAiI0M0REZFNiIsIHNpemUgPSAwLjA1KQ0KICAgICkNCiNQbG90IGJ5IFRlY2hTdXBwb3J0DQpwNyA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgIGdyb3VwX2J5KENodXJuLFRlY2hTdXBwb3J0KSAlPiUNCiAgICAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgICAgZ2dwbG90KGFlcyhUZWNoU3VwcG9ydCxjb3VudCxmaWxsID0gQ2h1cm4pKSsNCiAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikrDQogICAgIGNvb3JkX2ZsaXAoKSsNCiAgICAgZ2d0aXRsZSgiVGVjaFN1cHBvcnQgZGlzdHJpYnV0aW9uIikgKw0KICAgIGxhYnMoDQogICAgICAgIHggPSAiVGVjaCBTdXBwb3J0IiwNCiAgICAgICAgeSA9ICJGcmVxdWVuY3kiDQogICAgKSArDQogICAgdGhlbWUoDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoDQogICAgICAgICAgICBmaWxsID0gIndoaXRlIiwNCiAgICAgICAgICAgIGNvbG91ciA9ICJsaWdodGdyZXkiLA0KICAgICAgICAgICAgc2l6ZSA9IDAuNzUpLA0KICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAiI0M0REZFNiIsIHNpemUgPSAwLjA1KQ0KICAgICkNCiNQbG90IGJ5IFN0cmVhbWluZ1RWDQpwOCA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgIGdyb3VwX2J5KENodXJuLFN0cmVhbWluZ1RWKSAlPiUNCiAgICAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgICAgZ2dwbG90KGFlcyhTdHJlYW1pbmdUVixjb3VudCxmaWxsID0gQ2h1cm4pKSsNCiAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikrDQogICAgIGNvb3JkX2ZsaXAoKSsNCiAgICAgZ2d0aXRsZSgiU3RyZWFtaW5nVFYgZGlzdHJpYnV0aW9uIikgKw0KICAgIGxhYnMoDQogICAgICAgIHggPSAiU3RyZWFtaW5nVFYiLA0KICAgICAgICB5ID0gIkZyZXF1ZW5jeSINCiAgICApICsNCiAgICB0aGVtZSgNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgZmFjZSA9ICJib2xkIiksDQogICAgICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdCgNCiAgICAgICAgICAgIGZpbGwgPSAid2hpdGUiLA0KICAgICAgICAgICAgY29sb3VyID0gImxpZ2h0Z3JleSIsDQogICAgICAgICAgICBzaXplID0gMC43NSksDQogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAid2hpdGUiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9saW5lKGNvbG91ciA9ICIjQzRERkU2Iiwgc2l6ZSA9IDAuMDUpDQogICAgKQ0KI1Bsb3QgYnkgU3RyZWFtaW5nTW92aWVzDQpwOSA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgIGdyb3VwX2J5KENodXJuLFN0cmVhbWluZ01vdmllcykgJT4lDQogICAgIHN1bW1hcmlzZShjb3VudCA9IG4oKSkgJT4lDQogICAgIGdncGxvdChhZXMoU3RyZWFtaW5nTW92aWVzLGNvdW50LGZpbGwgPSBDaHVybikpKw0KICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZG9kZ2UiKSsNCiAgICAgY29vcmRfZmxpcCgpKw0KICAgICBnZ3RpdGxlKCJTdHJlYW1pbmdNb3ZpZXMgZGlzdHJpYnV0aW9uIikgKw0KICAgIGxhYnMoDQogICAgICAgIHggPSAiU3RyZWFtaW5nIE1vdmllcyIsDQogICAgICAgIHkgPSAiRnJlcXVlbmN5Ig0KICAgICkgKw0KICAgIHRoZW1lKA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KA0KICAgICAgICAgICAgZmlsbCA9ICJ3aGl0ZSIsDQogICAgICAgICAgICBjb2xvdXIgPSAibGlnaHRncmV5IiwNCiAgICAgICAgICAgIHNpemUgPSAwLjc1KSwNCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ3aGl0ZSIpLA0KICAgICAgICBwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2xpbmUoY29sb3VyID0gIiNDNERGRTYiLCBzaXplID0gMC4wNSkNCiAgICApDQojUGxvdCBieSBQYXBlcmxlc3NCaWxsaW5nDQpwMTAgPC0gdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICBncm91cF9ieShDaHVybixQYXBlcmxlc3NCaWxsaW5nKSAlPiUNCiAgICAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgICAgZ2dwbG90KGFlcyhQYXBlcmxlc3NCaWxsaW5nLGNvdW50LGZpbGwgPSBDaHVybikpKw0KICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZG9kZ2UiKSsNCiAgICAgY29vcmRfZmxpcCgpKw0KICAgICBnZ3RpdGxlKCJQYXBlcmxlc3NCaWxsaW5nIGRpc3RyaWJ1dGlvbiIpICsNCiAgICBsYWJzKA0KICAgICAgICB4ID0gIlBhcGVybGVzcyBCaWxsaW5nIiwNCiAgICAgICAgeSA9ICJGcmVxdWVuY3kiDQogICAgKSArDQogICAgdGhlbWUoDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoDQogICAgICAgICAgICBmaWxsID0gIndoaXRlIiwNCiAgICAgICAgICAgIGNvbG91ciA9ICJsaWdodGdyZXkiLA0KICAgICAgICAgICAgc2l6ZSA9IDAuNzUpLA0KICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAiI0M0REZFNiIsIHNpemUgPSAwLjA1KQ0KICAgICkNCiNQbG90IGJ5IENvbnRyYWN0DQpwMTEgPC0gdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICBncm91cF9ieShDaHVybixDb250cmFjdCkgJT4lDQogICAgIHN1bW1hcmlzZShjb3VudCA9IG4oKSkgJT4lDQogICAgIGdncGxvdChhZXMoQ29udHJhY3QsY291bnQsZmlsbCA9IENodXJuKSkrDQogICAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9ICJkb2RnZSIpKw0KICAgICBjb29yZF9mbGlwKCkrDQogICAgIGdndGl0bGUoIkNvbnRyYWN0IFR5cGUgZGlzdHJpYnV0aW9uIikgKw0KICAgIGxhYnMoDQogICAgICAgIHggPSAiQ29udHJhY3QgVHlwZSIsDQogICAgICAgIHkgPSAiRnJlcXVlbmN5Ig0KICAgICkgKw0KICAgIHRoZW1lKA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KA0KICAgICAgICAgICAgZmlsbCA9ICJ3aGl0ZSIsDQogICAgICAgICAgICBjb2xvdXIgPSAibGlnaHRncmV5IiwNCiAgICAgICAgICAgIHNpemUgPSAwLjc1KSwNCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ3aGl0ZSIpLA0KICAgICAgICBwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2xpbmUoY29sb3VyID0gIiNDNERGRTYiLCBzaXplID0gMC4wNSkNCiAgICApDQojUGxvdCBieSBQYXltZW50TWV0aG9kDQpwMTIgPC0gdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICBncm91cF9ieShDaHVybixQYXltZW50TWV0aG9kKSAlPiUNCiAgICAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgICAgZ2dwbG90KGFlcyhQYXltZW50TWV0aG9kLGNvdW50LGZpbGwgPSBDaHVybikpKw0KICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZG9kZ2UiKSsNCiAgICAgY29vcmRfZmxpcCgpKw0KICAgICBnZ3RpdGxlKCJQYXltZW50TWV0aG9kIGRpc3RyaWJ1dGlvbiIpICsNCiAgICBsYWJzKA0KICAgICAgICB4ID0gIlBheW1lbnQgTWV0aG9kIiwNCiAgICAgICAgeSA9ICJGcmVxdWVuY3kiDQogICAgKSArDQogICAgdGhlbWUoDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoDQogICAgICAgICAgICBmaWxsID0gIndoaXRlIiwNCiAgICAgICAgICAgIGNvbG91ciA9ICJsaWdodGdyZXkiLA0KICAgICAgICAgICAgc2l6ZSA9IDAuNzUpLA0KICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAiI0M0REZFNiIsIHNpemUgPSAwLjA1KQ0KICAgICkNCg0KZ3JpZC5hcnJhbmdlKHAxLHAyLHAzLHA0LHA1LHA2LHA3LHA4LHA5LHAxMCxwMTEscDEyKQ0KYGBgDQoNCioqT3RoZXIgQXR0cmlidXRlcyoqDQoNCi0tLQ0KDQpMZXQncyBsb29rIGF0IHRoZSBmb2xsb3dpbmcgdmFyaWFibGVzIGluIHRoaXMgc2VjdGlvbjoNCg0KKiBUZW51cmUNCiogTW9udGhseSBDaGFyZ2VzDQoqIE92ZXJhbGwgQ2hhcmdlcw0KDQpXZSBjYW4gbm90aWNlIHRoYXQgY3VzdG9tZXJzIHdobyBjaHVybmVkIHdlcmUgcHJlZG9taW5hbnRseSBmcm9tIGxvd2VyIHRlbnVyZSBncm91cHMuIFNvLCB0aGlzIGNvdWxkIGJlIGFuIGltcG9ydGFudCB2YXJpYWJsZSBmb3Igb3VyIG1vZGVsaW5nLiBPdmVyYWxsY2hhcmdlcyBhbHNvIGhhcyB0aGUgc2FtZSBkaXN0cmlidXRpb24gd2l0aCByZXNwZWN0IHRvIGNodXJuLCBidXQgdGhhdCBjb3VsZCBiZSBiZWNhdXNlIGl0IGhhcyBzdHJvbmcgY29ycmVsYXRpb24gd2l0aCB0ZW51cmUgKE92ZXJhbGxDaGFyZ2VzID0gVGVudXJlIHggTW9udGhseUNoYXJnZXMpLg0KDQpgYGB7ciBmaWcuaGVpZ2h0PTEwfQ0KI1RlbnVyZQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICAgcGxvdF9seSh4PSB+dGVudXJlLCBuYW1lID0gfkNodXJuLCB0eXBlID0gJ2hpc3RvZ3JhbScsIHNob3dsZWdlbmQgPSBULCB3aWR0aCA9IDUwMCwgaGVpZ2h0ID0gNDAwKSAlPiUNCiAgICAgbGF5b3V0KHRpdGxlID0gJ1RlbnVyZSBkaXN0cmlidXRpb24gYnkgQ2h1cm4nKQ0KDQojTW9udGhseSBDaGFyZ2VzDQp0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgICBwbG90X2x5KHg9IH5Nb250aGx5Q2hhcmdlcywgbmFtZSA9IH5DaHVybiwgdHlwZSA9ICdoaXN0b2dyYW0nLCBzaG93bGVnZW5kID0gVCwgd2lkdGggPSA1MDAsIGhlaWdodCA9IDQwMCkgJT4lDQogICAgIGxheW91dCh0aXRsZSA9ICdNb250aGx5IENoYXJnZXMgZGlzdHJpYnV0aW9uIGJ5IENodXJuJykNCg0KI092ZXJhbGwgQ2hhcmdlcw0KdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICAgcGxvdF9seSh4PSB+VG90YWxDaGFyZ2VzLCBuYW1lID0gfkNodXJuLCB0eXBlID0gJ2hpc3RvZ3JhbScsIHNob3dsZWdlbmQgPSBULCB3aWR0aCA9IDUwMCwgaGVpZ2h0ID0gNDAwKSAlPiUNCiAgICAgbGF5b3V0KHRpdGxlID0gJ1RvdGFsIENoYXJnZXMgZGlzdHJpYnV0aW9uIGJ5IENodXJuJykNCg0KYGBgDQoNCkJ5IHBsb3R0aW5nIFRlbnVyZSBhZ2FpbnN0IE1vbnRobHlDaGFyZ2VzLCB3ZSBjYW4gbm90aWNlIGluIHRoZSBiZWxvdyBncmFwaCB0aGF0IGNodXJuZWQgY3VzdG9tZXJzIGFyZSBtb3N0bHkgY29uY2VudHJhdGVkIGluIHRoZSBsb3cgdGVudXJlICsgaGlnaCBtb250aGx5IGNoYXJnZXMgcmVnaW9uLiBUaGVzZSB0d28gdmFyaWFibGVzIGNvdWxkIGJlIHVzZWZ1bCBpbiBvdXIgbW9kZWwuDQpgYGB7ciBtZXNzYWdlPUZBTFNFfQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICBwbG90X2x5KHggPSB+dGVudXJlLCB5ID0gfk1vbnRobHlDaGFyZ2VzLCBuYW1lID0gfkNodXJuLCB3aWR0aCA9IDEwMDAsIGhlaWdodCA9IDUwMCkgJT4lDQogICAgIGxheW91dCh0aXRsZSA9ICdNb250aGx5IENoYXJnZXMgdnMgVGVudXJlIGJ5IENodXJuJykNCmBgYA0KQnkgcGxvdHRpbmcgVGVudXJlIGFnYWluc3QgVG90YWxDaGFyZ2VzLCB3ZSBjYW4gbm90aWNlIGluIHRoZSBiZWxvdyBncmFwaCB0aGF0IGNodXJuZWQgY3VzdG9tZXJzIGFyZSBtb3N0bHkgY29uY2VudHJhdGVkIGluIHRoZSBsb3cgdGVudXJlICsgbG93IFRvdGFsQ2hhcmdlcyByZWdpb24uDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQp0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgIHBsb3RfbHkoeCA9IH50ZW51cmUsIHkgPSB+VG90YWxDaGFyZ2VzLCBuYW1lID0gfkNodXJuLCB3aWR0aCA9IDEwMDAsIGhlaWdodCA9IDUwMCkgJT4lDQogICAgIGxheW91dCh0aXRsZSA9ICdUb3RhbCBDaGFyZ2VzIHZzIFRlbnVyZSBieSBDaHVybicpDQpgYGANClBsb3R0aW5nIE1vbnRobHkgQ2hhcmdlcyBhZ2FpbnN0IFRvdGFsQ2hhcmdlcyBtYXkgbm90IGJlIHZlcnkgdXNlZnVsLCBidXQgbGV0J3Mgc2VlIGlmIHRoZSBwbG90IGNhbiBnaXZlIHVzIHNvbWUgaW5zaWdodC4NCkFzIHdlIGNhbiBzZWUgY2h1cm5lZCBjdXN0b21lcnMgYXJlIGluIHRoZSBsb3ctc2lkZSBvZiBUb3RhbENoYXJnZXMgKHRoZXNlIGNvdWxkIGJlIGN1c3RvbWVycyB3aXRoIGxvdyB0ZW51cmVzKS4NCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQp0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgIHBsb3RfbHkoeCA9IH5Nb250aGx5Q2hhcmdlcywgeSA9IH5Ub3RhbENoYXJnZXMsIG5hbWUgPSB+Q2h1cm4sIHdpZHRoID0gMTAwMCwgaGVpZ2h0ID0gNTAwKSAlPiUNCiAgICAgbGF5b3V0KHRpdGxlID0gJ01vbnRobHkgQ2hhcmdlcyB2cyBUb3RhbCBDaGFyZ2VzIGJ5IENodXJuJykNCmBgYA0KTGV0J3MgcGxvdCB0aGUgZGlzdHJpYnV0aW9uIG9mIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBzcGVjaWZpY2FsbHkgaW4gKipjaHVybmVkIGN1c3RvbWVycyoqIHRvIGNoZWNrIGlmIGFueSB2YXJpYWJsZSBzdGFuZHMgb3V0Lg0KDQoqKkN1c3RvbWVyIEF0dHJpYnV0ZXMgZGlzdHJpYnV0aW9uIG9uIGNodXJuZWQgY3VzdG9tZXJzKioNCg0KLS0tDQoNCldlIHNhdyBlYXJsaWVyIHRoYXQgU2VuaW9yIENpdGl6ZW4gYW5kIERlcGVuZGVudCB2YXJpYWJsZXMgY291bGQgaGF2ZSBjb3JyZWxhdGlvbiB3aXRoIENodXJuLCBsZXQncyBzZWUgd2hhdCBpcyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZXNlIHZhcmlhYmxlcyBpbiBjaHVybmVkIGN1c3RvbWVycy4gQXMgd2UgY2FuIHNlZSwgYWJvdXQgMjUlIG9mIHRoZSBjaHVybmVkIGN1c3RvbWVycyBhcmUgc2VuaW9yIGNpdGl6ZW5zIGFuZCAxNy40IHBlcmNlbnQgYXJlIHRob3NlIHdpdGggZGVwZW5kZW50cy4gU28sIHRoZXNlIGF0dHJpYnV0ZXMgbWF5IG5vdCBiZSBhcyB1c2VmdWwgYXMgd2UgdGhvdWdodCB0aGV5IGFyZSBhZnRlcmFsbC4NCg0KYGBge3J9DQp0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgICBmaWx0ZXIoQ2h1cm49PTEpICU+JQ0KICAgICAgcGxvdF9seShsYWJlbHMgPSB+U2VuaW9yQ2l0aXplbiwgdmFsdWVzID0gfkNodXJuLCB0eXBlID0gInBpZSIsIHdpZHRoID0gNTAwLCBoZWlnaHQgPSA0MDApICU+JQ0KICAgICAgbGF5b3V0KHRpdGxlID0gJ1NlbmlvckNpdGl6ZW5zIGRpc3RyaWJ1dGlvbiBpbiBDaHVybmVkIEN1c3RvbWVycycpDQoNCnRlbGNvX2N1c3RvbWVyX2NodXJuX2NsZWFuZWRfZGF0YSAlPiUNCiAgICAgIGZpbHRlcihDaHVybj09MSkgJT4lDQogICAgICBwbG90X2x5KGxhYmVscyA9IH5EZXBlbmRlbnRzLCB2YWx1ZXMgPSB+Q2h1cm4sIHR5cGUgPSAicGllIiwgd2lkdGggPSA2MDAsIGhlaWdodCA9IDQwMCkgJT4lDQogICAgICBsYXlvdXQodGl0bGUgPSAnRGVwZW5kZW50IGRpc3RyaWJ1dGlvbiBpbiBjaHVybmVkIGN1c3RvbWVycycpDQpgYGANCg0KKipTZXJ2aWNlIEF0dHJpYnV0ZXMgZGlzdHJpYnV0aW9uIG9uIGNodXJuZWQgY3VzdG9tZXJzKioNCg0KLS0tDQoNCldlIHNhdyBlYXJsaWVyIHRoYXQgUGF5bWVudCBNZXRob2QsICBDb250cmFjdCBUeXBlLCBQYXBlcmxlc3MgQmlsbGluZyBhbmQgSW50ZXJuZXQgU2VydmljZSBUeXBlIGF0dHJpYnV0ZXMgY291bGQgaGF2ZSBjb3JyZWxhdGlvbiB3aXRoIENodXJuLCBsZXQncyBzZWUgd2hhdCBpcyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZXNlIHZhcmlhYmxlcyBpbiBjaHVybmVkIGN1c3RvbWVycy4NCg0KQXMgd2UgY2FuIG5vdGljZSBmcm9tIHRoZSBncmFwaHMgYmVsb3csIGFsbCB0aGUgNCB2YXJpYWJsZXMgaGF2ZSBhIHNwZWNpZmljIGNhdGVnb3J5IHRoYXQgY29udHJpYnV0ZXMgdG8gbW9yZSB0aGFuIDUwJSBvZiBjaHVybmVkIGN1c3RvbWVycy4gVGhlc2UgdmFyaWFibGVzIGNvdWxkIGJlIHZlcnkgdXNlZnVsIGZvciBvdXIgbW9kZWxzLg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInfQ0KdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICAgZmlsdGVyKENodXJuPT0xKSAlPiUNCiAgICAgIHBsb3RfbHkobGFiZWxzID0gflBheW1lbnRNZXRob2QsIHZhbHVlcyA9IH5DaHVybiwgdHlwZSA9ICJwaWUiLCB3aWR0aCA9IDYwMCwgaGVpZ2h0ID0gNDAwKSAlPiUNCiAgICAgIGxheW91dCh0aXRsZSA9ICdQYXltZW50IE1ldGhvZCBkaXN0cmlidXRpb24gaW4gQ2h1cm5lZCBDdXN0b21lcnMnKQ0KDQp0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgICBmaWx0ZXIoQ2h1cm49PTEpICU+JQ0KICAgICAgcGxvdF9seShsYWJlbHMgPSB+Q29udHJhY3QsIHZhbHVlcyA9IH5DaHVybiwgdHlwZSA9ICJwaWUiLCB3aWR0aCA9IDYwMCwgaGVpZ2h0ID0gNDAwKSAlPiUNCiAgICAgIGxheW91dCh0aXRsZSA9ICdDb250cmFjdFR5cGUgZGlzdHJpYnV0aW9uIGluIGNodXJuZWQgY3VzdG9tZXJzJykNCg0KdGVsY29fY3VzdG9tZXJfY2h1cm5fY2xlYW5lZF9kYXRhICU+JQ0KICAgICAgZmlsdGVyKENodXJuPT0xKSAlPiUNCiAgICAgIHBsb3RfbHkobGFiZWxzID0gflBhcGVybGVzc0JpbGxpbmcsIHZhbHVlcyA9IH5DaHVybiwgdHlwZSA9ICJwaWUiLCB3aWR0aCA9IDYwMCwgaGVpZ2h0ID0gNDAwKSAlPiUNCiAgICAgIGxheW91dCh0aXRsZSA9ICdQYXBlcmxlc3NCaWxsaW5nIGRpc3RyaWJ1dGlvbiBpbiBjaHVybmVkIGN1c3RvbWVycycpDQoNCnRlbGNvX2N1c3RvbWVyX2NodXJuX2NsZWFuZWRfZGF0YSAlPiUNCiAgICAgIGZpbHRlcihDaHVybj09MSkgJT4lDQogICAgICBwbG90X2x5KGxhYmVscyA9IH5JbnRlcm5ldFNlcnZpY2UsIHZhbHVlcyA9IH5DaHVybiwgdHlwZSA9ICJwaWUiLCB3aWR0aCA9IDYwMCwgaGVpZ2h0ID0gNDAwKSAlPiUNCiAgICAgIGxheW91dCh0aXRsZSA9ICdJbnRlcm5ldFNlcnZpY2UgZGlzdHJpYnV0aW9uIGluIGNodXJuZWQgY3VzdG9tZXJzJykNCmBgYA0KIyMjUmVzdWx0cyBvZiBFREENCg0KLS0tDQoNCkJhc2VkIG9uIHRoZSB0cmVuZHMgd2Ugbm90aWNlZCBpbiBFREEsIHdlIGZvdW5kIHRoYXQgdGhlIGZvbGxvd2luZyB2YXJpYWJsZXMgY291bGQgYmUgdmVyeSBpbXBvcnRhbnQgdG8gcHJlZGljdCBjdXN0b21lciBjaHVybg0KDQoqIFBheW1lbnQgTWV0aG9kIChFbGVjdHJvbmljQ2hlY2spDQoqIFBhcGVybGVzc0JpbGxpbmcNCiogQ29udHJhY3RUeXBlIChNb250aC10by1Nb250aCkNCiogSW50ZXJuZXRTZXJ2aWNlDQoqIFRlbnVyZQ0KKiBNb250aGx5Q2hhcmdlcw0KDQpOb3RpY2UgdGhhdCB0aGVzZSBhcmUgYWxsIHNlcnZpY2UgYXR0cmlidXRlcyBhbmQgKipub3QqKiBjdXN0b21lciBhdHRyaWJ1dGVzLiBNYXkgYmUgdGhlIGNvbXBhbnkgbXVzdCBsb29rIGF0IHNlcnZpY2UgYXR0cmlidXRlcyB0byBjb250cm9sIGNodXJuIG1vcmUgdGhhbiBjdXN0b21lbXIgYXR0cmlidXRlcy4gVGhpcyBjb3VsZCBiZSBhIGdvb2QgZ2VuZXJhbCBndWlkaW5nIHByaW5jaXBsZSBmb3IgdGhlIGNvbXBhbnkuIA0KTGV0J3MgcHJvY2VlZCB0byBtb2RlbGluZyBub3cgYW5kIHVzZSB0aGVzZSBhdHRyaWJ1dGVzLg0KDQojIyNNb2RlbGluZw0KDQotLS0NCg0KRHVtbWlmeSB0aGUgZGF0YSBzbyB0aGF0IGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBhcmUgY29udmVydGVkIHRvIG11bHRpcGxlIGNvbHVtbnMgd2l0aCAwLzFzLg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCnRlbGNvX2N1c3RvbWVyX2NodXJuX2R1bW1pZmllZCA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl9jbGVhbmVkX2RhdGEgJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHVtbWlmeSgpICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZShDaHVybl95ZXMgPSBhcy5mYWN0b3IoaWZlbHNlKENodXJuXzE9PTEsICJZZXMiLCAiTm8iKSkpDQpgYGANCg0KUGFydGl0aW9uIGRhdGEgaW50byBUcmFpbmluZyBhbmQgVmFsaWRhdGlvbiBkYXRhc2V0cyB3aXRoIGEgNzAtMzAgc3BsaXQgb2YgdGhlIGZ1bGwgZGF0YXNldC4NCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQpzZXQuc2VlZCgxMCkNCg0KaW5UcmFpbmluZ0RhdGEgPC0gY3JlYXRlRGF0YVBhcnRpdGlvbih0ZWxjb19jdXN0b21lcl9jaHVybl9kdW1taWZpZWQkQ2h1cm5feWVzLCBwPTAuNzAsIGxpc3Q9RkFMU0UpDQoNCnRyYWluaW5nLnNldCA8LSB0ZWxjb19jdXN0b21lcl9jaHVybl9kdW1taWZpZWRbaW5UcmFpbmluZ0RhdGEsXQ0KDQpUb3RhbHZhbGlkYXRpb24uc2V0IDwtIHRlbGNvX2N1c3RvbWVyX2NodXJuX2R1bW1pZmllZFstaW5UcmFpbmluZ0RhdGEsXQ0KDQpgYGANCg0KIyMjI1JhbmRvbSBGb3Jlc3QNCg0KLS0tDQoNCkxldCdzIHRyeSBzb21lIG10cnkgdmFsdWVzIGFuZCBzZWUgd2hpY2ggdmFsdWUgZ2l2ZXMgdXMgYSBsb3dlciBPT0IgZXJyb3IgcGVyY2VudGFnZS4gDQoNCkFmdGVyIHJlcGVhdGVkIHRyaWFscywgbXRyeSA1IHdhcyBmb3VuZCB0byBnaXZlIHRoZSBsb3dlc3QgT09CIGVycm9yIG9mIGFyb3VuZCAyMSUgYXMgc2hvd24gYmVsb3cuDQoNCkFzIHdlIGNhbiBzZWUgZnJvbSB0aGUgKipWYXJpYWJsZSBJbXBvcnRhbmNlIFBsb3RzKiogYmVsb3csIHRoZSBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMgZm9yIHByZWRpY3RpbmcgQ2h1cm4gYXJlDQoNCiogdGVudXJlDQoqIFRvdGFsQ2hhcmdlcw0KKiBNb250aGx5Q2hhcmdlcw0KKiBDb250cmFjdF9Nb250aC50by5tb250aA0KKiBJbnRlcm5ldFNlcnZ1Y2VfRmliZXIub3B0aWMNCiogUGF5bWVudE1ldGhvZC5FbGVjdHJvbmljLmNoZWNrDQoNCndoaWNoIGlzIHByZXR0eSBtdWNoIGNvbnNpc3RlbnQgd2l0aCB3aGF0IHdlIGZvdW5kIGluIG91ciBFREEuDQoNCmBgYHtyIGZpZy5oZWlnaHQ9MTAsIG1lc3NhZ2U9RkFMU0V9DQp0cmFpbmluZy5zZXQgPC0gdHJhaW5pbmcuc2V0ICU+JQ0KICAgICAgICAgICAgICAgICAgICBzZWxlY3QoLWN1c3RvbWVySUQsIC1DaHVybl8wLCAtQ2h1cm5fMSkgDQoNCiNyZl9maXRfY2hlY2sgPC0gcmFuZG9tRm9yZXN0KENodXJuX3llcyB+LiwgZGF0YSA9IHRyYWluaW5nLnNldCwgaW1wb3J0YW5jZSA9IFRSVUUsIG1ldHJpYyA9ICJST0MiLCBudHJlZSA9IDEwMDEsIG10cnkgPSAyKQ0KI3JmX2ZpdF9jaGVjayA8LSByYW5kb21Gb3Jlc3QoQ2h1cm5feWVzIH4uLCBkYXRhID0gdHJhaW5pbmcuc2V0LCBpbXBvcnRhbmNlID0gVFJVRSwgbWV0cmljID0gIlJPQyIsIG50cmVlID0gMTAwMSwgbXRyeSA9IDMpDQojcmZfZml0X2NoZWNrIDwtIHJhbmRvbUZvcmVzdChDaHVybl95ZXMgfi4sIGRhdGEgPSB0cmFpbmluZy5zZXQsIGltcG9ydGFuY2UgPSBUUlVFLCBtZXRyaWMgPSAiUk9DIiwgbnRyZWUgPSAxMDAxLCBtdHJ5ID0gNCkNCnJmX2ZpdF9jaGVjayA8LSByYW5kb21Gb3Jlc3QoQ2h1cm5feWVzIH4uLCBkYXRhID0gdHJhaW5pbmcuc2V0LCBpbXBvcnRhbmNlID0gVFJVRSwgbWV0cmljID0gInJvYyIsIG50cmVlID0gMTAwMSwgbXRyeSA9IDUpDQoNClZhcmlhYmxlSW1wDQp2YXJJbXBQbG90KFZhcmlhYmxlSW1wKQ0KYGBgDQoNCkxldCdzIHVzZSB0aGVzZSB2YXJpYWJsZSBhbmQgYnVpbGQgYSBSYW5kb20gRm9yZXN0IG1vZGVsLg0KDQoNCmBgYHtyIFJhbmRvbSBGb3Jlc3R9DQpjb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAicmVwZWF0ZWRjdiIsIG51bWJlciA9IDEwLCByZXBlYXRzPTMsIHNlYXJjaCA9ICJncmlkIiwgc3VtbWFyeUZ1bmN0aW9uPXR3b0NsYXNzU3VtbWFyeSwgY2xhc3NQcm9icyA9IFRSVUUpDQpzZXQuc2VlZCgxMCkNCnR1bmVncmlkIDwtIGV4cGFuZC5ncmlkKC5tdHJ5PTUpDQpyZl9maXQgPC0gdHJhaW4oQ2h1cm5feWVzIH4gdGVudXJlKw0KICAgICAgICAgICAgICAgICAgICAgTW9udGhseUNoYXJnZXMrDQogICAgICAgICAgICAgICAgICAgICBUb3RhbENoYXJnZXMrDQogICAgICAgICAgICAgICAgICAgICBDb250cmFjdF9Nb250aC50by5tb250aCsNCiAgICAgICAgICAgICAgICAgICAgIEludGVybmV0U2VydmljZV9GaWJlci5vcHRpYywNCiAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW5pbmcuc2V0LA0KICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJyZiIsDQogICAgICAgICAgICAgICAgbWV0cmljID0gIlJPQyIsDQogICAgICAgICAgICAgICAgdHVuZWdyaWQ9dHVuZWdyaWQsDQogICAgICAgICAgICAgICAgdHJDb250cm9sPWNvbnRyb2wsDQogICAgICAgICAgICAgICAgcHJlUHJvY2VzcyA9IGMoImNlbnRlciIsICJzY2FsZSIpLA0KICAgICAgICAgICAgICAgIG50cmVlPTEwMDEpDQpgYGANCg0KTGV0J3MgbG9vayBhdCB0aGUgbW9kZWwgc3Rpc3RpY3Mgb2YgdGhlIFJhbmRvbSBGb3Jlc3QgbW9kZWwuIA0KDQpBcyB3ZSBjYW4gc2VlIHRoZSBiZXN0IFJPQyB3YXMgYWNoaWV2ZWQgYXQgbXRyeSA9IDIuIFRoZSBtb2RlbCBoYXMgaGlnaCBudW1iZXIgb2YgZmFsc2UgbmVnYXRpdmVzIChudW1iZXIgb2YgY2h1cm5lZCBjdXN0b21lcnMgd2hvIHdlcmUgY2xhc3NpZmllZCBhcyBOb3QgQ2h1cm5lZCkuIExldCdzIHNlZSBpZiBMb2dpc3RpYyBSZWdyZXNzaW9uIGNhbiBnaXZlIHVzIGJldHRlciBwcmVkaWN0aW9ucyBpbiB0aGUgbmV4dCBzZWN0aW9uLg0KYGBge3J9DQpwcmludChyZl9maXQpDQoNCnBsb3QocmZfZml0KQ0KDQoNCnByZWRfdGVzdCA8LSBwcmVkaWN0KHJmX2ZpdCxuZXdkYXRhID0gVG90YWx2YWxpZGF0aW9uLnNldCkNCg0KY29uZnVzaW9uTWF0cml4KGRhdGE9cHJlZF90ZXN0LCBUb3RhbHZhbGlkYXRpb24uc2V0JENodXJuX3llcykNCg0KcmVzdWx0c18xIDwtIGNvbmZ1c2lvbk1hdHJpeChkYXRhPXByZWRfdGVzdCwgVG90YWx2YWxpZGF0aW9uLnNldCRDaHVybl95ZXMpDQpjb25mdXNpb25fbWF0MSA8LSBhcy5kYXRhLmZyYW1lKHJlc3VsdHNfMSR0YWJsZSkNCg0KZ2dwbG90KGRhdGEgPSAgY29uZnVzaW9uX21hdDEsIG1hcHBpbmcgPSBhZXMoeCA9IFJlZmVyZW5jZSwgeSA9IFByZWRpY3Rpb24pKSArDQogIGdndGl0bGUoIkNvbmZ1c2lvbiBNYXRyaXggZm9yIFJhbmRvbSBGb3Jlc3QiKSArDQogIGdlb21fdGlsZShhZXMoZmlsbCA9IEZyZXEpLCBjb2xvdXIgPSAid2hpdGUiKSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBzcHJpbnRmKCIlMS4wZiIsIEZyZXEpKSwgdmp1c3QgPSAxKSArDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gIiNCMkRCRDUiLCBoaWdoID0gIiMyQjYxNkQiKSArDQogIHRoZW1lX2J3KCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIpKQ0KDQpgYGANCg0KIyMjI0xvZ2lzdGljIFJlZ3Jlc3Npb24NCg0KLS0tDQoNCkxldCdzIHRyeSB0byBmaW5kIG91dCB3aGljaCBwcmVkaWN0b3JzIGFyZSBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50IGZvciBhIEdMTSBtb2RlbC4NCg0KQXMgd2UgY2FuIHNlZSwgdGhlIGZvbGxvd2luZyB2YXJpYWJsZXMgYXJlIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgaW4gdGhpcyBtb2RlbDoNCg0KKiB0ZW51cmUNCiogdG90YWxDaGFyZ2VzDQoqIENvbnRyYWN0X01vbnRoLnRvLm1vbnRoDQoqIFBhcGVybGVzc0JpbGxpbmdfTm8NCiogUGF5bWVudE1ldGhvZF9FbGVjdHJvbmljLmNoZWNrDQoNCmFzIHRoZXkgaGF2ZSBsb3cgUC12YWx1ZXMuIFRoaXMgaXMgYWxtb3N0IHNpbWlsYXIgdG8gd2hhdCB3ZSBmb3VuZCBpbiBSYW5kb20gRm9yZXN0LCBleGNlcHQgdGhhdCBNb250aGx5Q2hhcmdlcyBoYXMgbm90IGJlZW4gZm91bmQgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBoZXJlLg0KDQpgYGB7cn0NCnNldC5zZWVkKDEwKQ0KDQpnbG1fZml0X2NoZWNrIDwtIGdsbShDaHVybl95ZXMgfi4sIGRhdGEgPSB0cmFpbmluZy5zZXQsIGZhbWlseSA9ICdiaW5vbWlhbCcpDQoNCnN1bW1hcnkoZ2xtX2ZpdF9jaGVjaykNCmBgYA0KTGV0O3MgYnVpbGQgYSByZWdyZXNzaW9uIG1vZGVsIHVzaW5nIHRoZXNlIHZhcmlhYmxlcyBhbmQgY2hlY2sgaG93IHRoZXkgcHJlZGljdCBjaHVybi4NCmBgYHtyfQ0KdHJhaW5pbmcuc2V0LjIgPC0gdHJhaW5pbmcuc2V0ICU+JQ0KICAgICAgICAgICAgICAgICAgICBtdXRhdGUoQ2h1cm5feWVzID0gYXMuZmFjdG9yKGlmZWxzZShDaHVybl95ZXM9PSJZZXMiLDEsMCkpKQ0KVG90YWx2YWxpZGF0aW9uLnNldC4yIDwtIFRvdGFsdmFsaWRhdGlvbi5zZXQgJT4lDQogICAgICAgICAgICAgICAgICAgIG11dGF0ZShDaHVybl95ZXMgPSBhcy5mYWN0b3IoaWZlbHNlKENodXJuX3llcz09IlllcyIsMSwwKSkpDQoNCmNvbnRyb2xfcmVnIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAicmVwZWF0ZWRjdiIsIG51bWJlciA9IDEwLCByZXBlYXRzID0gMykNCnNldC5zZWVkKDEwKQ0KcmVnX2ZpdCA8LSB0cmFpbihDaHVybl95ZXMgfiB0ZW51cmUrDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRvdGFsQ2hhcmdlcysNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ29udHJhY3RfTW9udGgudG8ubW9udGgrDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFBhcGVybGVzc0JpbGxpbmdfTm8rDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFBheW1lbnRNZXRob2RfRWxlY3Ryb25pYy5jaGVjaysNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ29udHJhY3RfT25lLnllYXIsDQogICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluaW5nLnNldC4yLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJnbG0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGZhbWlseSA9ICJiaW5vbWlhbCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgcHJlUHJvY2VzcyA9IGMoImNlbnRlciIsInNjYWxlIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gY29udHJvbF9yZWcpDQpgYGANCg0KQXMgd2UgY2FuIHNlZSB0aGUgYWNjdXJhY3kgbGV2ZWwgaGFzIGRyb3BwZWQgdG8gNzclLg0KYGBge3J9DQpwcmludChyZWdfZml0KQ0KDQpwcmVkX3Rlc3RfcmVnIDwtIHByZWRpY3QocmVnX2ZpdCxuZXdkYXRhID0gVG90YWx2YWxpZGF0aW9uLnNldC4yKQ0KDQpjb25mdXNpb25NYXRyaXgoZGF0YT1wcmVkX3Rlc3RfcmVnLCBUb3RhbHZhbGlkYXRpb24uc2V0LjIkQ2h1cm5feWVzKQ0KDQpyZXN1bHRzXzIgPC0gY29uZnVzaW9uTWF0cml4KGRhdGE9cHJlZF90ZXN0X3JlZywgVG90YWx2YWxpZGF0aW9uLnNldC4yJENodXJuX3llcykNCmNvbmZ1c2lvbl9tYXQyIDwtIGFzLmRhdGEuZnJhbWUocmVzdWx0c18yJHRhYmxlKQ0KDQpnZ3Bsb3QoZGF0YSA9ICBjb25mdXNpb25fbWF0MiwgbWFwcGluZyA9IGFlcyh4ID0gUmVmZXJlbmNlLCB5ID0gUHJlZGljdGlvbikpICsNCiAgZ2d0aXRsZSgiQ29uZnVzaW9uIE1hdHJpeCBmb3IgUmVncmVzc2lvbiBJdGVyYXRpb24gMSIpICsNCiAgZ2VvbV90aWxlKGFlcyhmaWxsID0gRnJlcSksIGNvbG91ciA9ICJ3aGl0ZSIpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHNwcmludGYoIiUxLjBmIiwgRnJlcSkpLCB2anVzdCA9IDEpICsNCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAiI0IyREJENSIsIGhpZ2ggPSAiIzJCNjE2RCIpICsNCiAgdGhlbWVfYncoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgZmFjZSA9ICJib2xkIikpDQoNCmBgYA0KDQpMZXQncyB0cnkgdG8gcnVuIHJlZ3Jlc3Npb24gdXNpbmcgdGhlIHNhbWUgdmFyaWFibGVzIHRoYXQgd2UgdXNlZCBpbiBSYW5kb20gRm9yZXN0IGFuZCBjaGVjayB0aGUgcmVzdWx0cw0KYGBge3J9DQpjb250cm9sX3JlZ18yIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAicmVwZWF0ZWRjdiIsIG51bWJlciA9IDEwLCByZXBlYXRzID0gMykNCnNldC5zZWVkKDEwKQ0KcmVnX2ZpdF8yIDwtIHRyYWluKENodXJuX3llcyB+IHRlbnVyZSsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNb250aGx5Q2hhcmdlcysNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUb3RhbENoYXJnZXMrDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ29udHJhY3RfTW9udGgudG8ubW9udGgrDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW50ZXJuZXRTZXJ2aWNlX0ZpYmVyLm9wdGljLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbmluZy5zZXQuMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAiZ2xtIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBmYW1pbHkgPSAiYmlub21pYWwiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHByZVByb2Nlc3MgPSBjKCJjZW50ZXIiLCJzY2FsZSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGNvbnRyb2xfcmVnXzIpDQoNCmBgYA0KDQpBcyB3ZSBjYW4gc2VlIHRoZSBhY2N1cmFjeSBsZXZlbCBpcyBzdGlsbCA3NyUuIEFuZCB0aGUgZmFsc2UgbmVnYXRpdmVzIGFyZSBzdGlsbCBvbiB0aGUgaGlnaGVyIHNpZGUuDQpgYGB7cn0NCnByaW50KHJlZ19maXRfMikNCg0KcHJlZF90ZXN0X3JlZ18yIDwtIHByZWRpY3QocmVnX2ZpdF8yLG5ld2RhdGEgPSBUb3RhbHZhbGlkYXRpb24uc2V0LjIpDQoNCmNvbmZ1c2lvbk1hdHJpeChkYXRhPXByZWRfdGVzdF9yZWcsIFRvdGFsdmFsaWRhdGlvbi5zZXQuMiRDaHVybl95ZXMpDQoNCnJlc3VsdHNfMyA8LSBjb25mdXNpb25NYXRyaXgoZGF0YT1wcmVkX3Rlc3RfcmVnLCBUb3RhbHZhbGlkYXRpb24uc2V0LjIkQ2h1cm5feWVzKQ0KY29uZnVzaW9uX21hdDMgPC0gYXMuZGF0YS5mcmFtZShyZXN1bHRzXzMkdGFibGUpDQoNCmdncGxvdChkYXRhID0gIGNvbmZ1c2lvbl9tYXQzLCBtYXBwaW5nID0gYWVzKHggPSBSZWZlcmVuY2UsIHkgPSBQcmVkaWN0aW9uKSkgKw0KICBnZ3RpdGxlKCJDb25mdXNpb24gTWF0cml4IGZvciBSZWdyZXNzaW9uIEl0ZXJhdGlvbiAyIikgKw0KICBnZW9tX3RpbGUoYWVzKGZpbGwgPSBGcmVxKSwgY29sb3VyID0gIndoaXRlIikgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gc3ByaW50ZigiJTEuMGYiLCBGcmVxKSksIHZqdXN0ID0gMSkgKw0KICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICIjQjJEQkQ1IiwgaGlnaCA9ICIjMkI2MTZEIikgKw0KICB0aGVtZV9idygpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLCBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBmYWNlID0gImJvbGQiKSkNCmBgYA0KDQojIyNDb25jbHVzaW9ucw0KDQotLS0NCg0KQmV0d2VlbiB0aGUgdHdvIG1vZGVscyB1c2VkLCBSYW5kb20gRm9yZXN0IGlzIHRoZSB3aW5uZXIgYXMgaXQgaGFzIGJldHRlciBhY2N1cmFjeS4NCg0KVGhlIGZvbGxvd2luZyBjb25jbHVzaW9ucyBjYW4gYmUgZHJhd24gZnJvbSB0aGlzIGFuYWx5c2lzOg0KDQoqIENodXJuIHNlZW1zIHRvIGJlIG1vcmUgYWZmZWN0ZWQgYnkgc2VydmljZSBhdHRyaWJ1dGVzIHRoYW4gY3VzdG9tZXIgZGVtb2dyYXBoaWMgYXR0cmlidXRlcy4NCiogQ3VzdG9tZXJzIHdobyBoYXZlIGJlZW4gYXNzb2NpYXRlZCB3aXRoIHRoZSBjb21wYW55IGxvbmdlciBzZWVtcyBsZXNzIGxpa2VseSB0byBjaHVybi4NCiogQ3VzdG9tZXJzIHdpdGggaGlnaCBtb250aGx5IGNoYXJnZXMgYXJlIG1vcmUgbGlrZWx5IHRvIGNodXJuLg0KKiBDdXN0b21lcnMgd2hvIGhhdmUgRmliZXIgT3B0aWMgaW50ZXJuZXQgY29ubmVjdGlvbiBhcmUgbW9yZSBsaWtlbHkgdG8gY2h1cm4uDQoNCg==